The Programs of the Week Before April Fools Us
This Week’s Program: Mar 27 - Mar 31
It’s been a productive week! My commits this week felt all noodled and scattered, but I’m really pleased with the direction the code is taking.
Last week I hit the big milestone of calling into a C function from my
own little Racket ffi/unsafe/introspection
library. This week has
been all about making sure my library’s API is ergonomic and
expressive, and also do what it needs to do to work. This week I
finished reading the Racket Guide. I feel pretty comfortable
working with Racket code and am now regularly referring to
the Reference to get a better understanding of the
language and the standard library. Racket is an interesting
language, and I’m enjoying it. I miss some of the niceties of
Clojure (like its awesome destructuring), but am able to push
through and do some interesting things.
2034968dbad88402c0fcc30ae2caa4295f980586
Look at this commit on Monday. On Monday I was such a foolhardy child,
thinking I could just plaster Macros everywhere and they
would solve all my problems. define-syntax
and its associated forms
are incredibly powerful tools and on Monday I thought they were just
the thing I needed to make my API sing! A typical young Schemer’s
foolish mistake.
86236cd315f58b5830e81f6336482370c8e0d817
The problem with using Macros and define-syntax
is that by the time
we have our FFI bindings, we’re already passed that phase of the
program. It’s runtime! Past the point of manipulating
syntax. Everything I do here has to be done dynamically, which makes
sense since GObject Introspection is all about dynamic interop.
So I have to find a way to make this code as expressive as
possible without manipulating the syntax to my bidding. The first
thing I do is reach for struct
. Part of what
I’m struggling with is littering my API with anonymous lambdas
that have no real useful metadata associated with them. I want to
have handy mechanisms for understanding the C pointers and types
I’m getting back. This is a convenience that the
previous gir
library just
doesn’t have.
In this commit I do things like
use procedure-rename
to add some affordances to
my code. And I use struct
. Little did I know how powerful Racket’s
structs
are…
ccb4287ddc56a106de511980913a5d134e4a880f
Racket has something called Structure Type Properties
that allows a structure type to behave like some other thing. An
awesome example of this is
the prop:procedure
property. This property
allows a struct
to behave like a lambda! This is perfect for
something like GObject Introspection functions where you want to
encapsulate parts of the function, but then also want a convenient way
to invoke the function. That’s exactly what I do here.
For most of the remaining commits, I use structs
and
prop:procedure
to make my API clean, implementing some of the other
parts of GIR. I also reorganized the code and tried to group things
according to what kind of type they were associated with.
964d02fada017e1dde828fc139ccd85e3fa03053
It was when I was wiring up method calls
for GIStructInfo
that I had my next aha
moment. Methods are a special kind of function in that their arity
reports as one less than what they really are. The first argument to a
method is implicitly a pointer to the object or struct that owns the
method. In order to call a method dynamically with GIR, I need to pass
in a pointer to a caller.
Racket’s FFI library includes a Structure Type Property
called prop:cpointer
that allows a struct to
“be used transparently as a C pointer value”. That means I can have my
struct
representing C types and just pass instances of those around
as pointers to their same types. Some real Racket/C interop going on
here. This might not sound like much, but it helps me unify my API in
a major way.
9995465df90e23204dc733d414a47d8976d4f8a8
This leads us here. I now have a uniform way of dealing with GIR
information types. Every Introspected type is represented by a
struct
that has properties that allow it to behave both as a
procedure and as a pointer for a C type. When called as a
procedure, each type behaves slightly differently. A GI Function
will invoke its function. A GI Constant will return its value. A
GI Struct will take a method name and return a GI Function ready
to be invoked.
168d7f6fac5babc430c5c748a6c819111ff48509
In addition to providing this uniformity throughout the interface, I also add in some convenient functions to see more about the C types I’m working with.
For example, calling describe-gi-function
on a function will
give me a string describing it’s type and arguments. Like so, for
GStreamer’s init_check
function:
init_check (gint32 argc, array* argv) → gboolean
Or for a Struct, like GIRepository’s own BaseInfo
:
struct BaseInfo {
gint32 dummy1
gint32 dummy2
void* dummy3
void* dummy4
void* dummy5
guint32 dummy6
guint32 dummy7
array padding
equal (BaseInfo* info2) → gboolean
get_attribute (utf8* name) → utf8*
get_container () → BaseInfo*
get_name () → utf8*
get_namespace () → utf8*
get_type () → InfoType
get_typelib () → Typelib*
is_deprecated () → gboolean
iterate_attributes (AttributeIter iterator, utf8* name, utf8* value) → gboolean
}
What the heck is going on with those “dummy” fields? Who knows‽ All I know is that I can look at these C types within Racket now and know what the heck they are!
I think I’ve got a few more weeks left working on my GIR lib before I can say that it’s good enough and I can move on to focus on bindings for GStreamer.
Try not to get too wacky for April Fools!
🤡 Mark