/tinyletter

This Week's Program

This Week’s Program: Apr 24 - Apr 28

I couldn’t come up with a catchy subject line this week. I was too busy jamming on this code.

cabda551d376937c1a3a97396dbb6f6e1570b987

“Let’s make a macro!” it says in the commit message. Once again, I dip into the define-syntax well. This time, I think to myself, I will finally find an appropriate application of macros.

The gir/require macro takes a namespace symbol and a list of symbols, looks up those symbols with GObject Introspection, and binds identifiers to those values. e.g.

(gir/require 'Gst (init_check version_string ElementFactory))

Would create the identifiers init-check, version-string, and element-factory and bind them to the respective forms from the C GStreamer library.

678f2353386360eeb401d2e2aaf34511544a5790

I create gstreamer/main.rkt. I also provide a bunch of bindings from my introspection library. Using the bindings that I got with gir/require I initialize GStreamer and print out its version.

When I provide bindings out of the introspection library I attach Racket contracts to them. From the Racket Guide:

Like a contract between two business partners, a software contract is an agreement between two parties. The agreement specifies obligations and guarantees for each “product” (or value) that is handed from one party to the other.

A contract thus establishes a boundary between the two parties. Whenever a value crosses this boundary, the contract monitoring system performs contract checks, making sure the partners abide by the established contract.

Contracts are a whole thing, and I had first heard of Design By Contract™ (yeah it’s really trademarked) when reading about the Eiffel programming language. Eiffel will tell you it’s about eliminating bugs, which is great and also not true. The value that Contracts provide is a form of documentation.

The contract for the introspection function says:

(->* (symbol?) (string?) gi-repository?)

You can read this like: The introspection function accepts a Symbol and an optional String and will return a GI-Repository.

In my REPL I can not do this and get a helpful message when I violate the contract:

> (introspection "Gst" 7)
; introspection: contract violation
;   expected: symbol?
;   given: "Gst"
;   in: the 1st argument of
;       (->* (symbol?) (string?) gi-repository?)
;   contract from:
;       <pkgs>/overscan/ffi/unsafe/introspection.rkt
;   blaming: <pkgs>/overscan/gstreamer/main.rkt
;    (assuming the contract is correct)
;   at: <pkgs>/overscan/ffi/unsafe/introspection.rkt:39.24

A contract can tell you what you’re doing wrong when calling a function and what that function’s expectations are. This is useful if, like me, you prefer to bash your code with your keyboard over and over again until it works.

93c6294c1ac493b52b4eaa4ad4e717e657cc604a

I don’t want to say “all macros are dumb” but this one is dumb so I remove it. It just doesn’t provide that much value over the procedures. I learned a lot about writing macros in the process. Something about it being the journey and not the destination or whatever. Get outta here gir/require.

9dc4b8f4ee3c31bb4ecde770362757f602960281

And while we’re at it, I’ll remove last week’s eval call to create a class. Within the context of the GStreamer module, this didn’t work. There’s a thing about eval and namespaces that I just don’t quite feel like learning about, and this is some extraneous code anyway. Instead of using racket/class, I’ll create an API for GObjects that matches racket/class conventions.

Now, instead of creating a class with quasiquotes and instantiating that when I get a pointer from C, I’ll just put that pointer into a new struct called gobject that inherits from gtype-instance. Then, I create a bunch of functions like those from the racket/class library, like dynamic-send, dynamic-get-field, and dynamic-set-field!.

332c982140e8021b6faaf2026ed1aa7b56a8e585

Now is the time to bring back macros and this time they stick around! The send macro, like its dual from racket/class, is a macro for invoking a method on an object. It just calls to dynamic-send, but I want to give the user here a real OOP experience.

In another commit I write get-field, set-field!, and field-bound? to round out the API compatibility with racket/class. Finally, I learned how to use a damn macro.

ee71c2689aa15f2a2db1acef1d7867d7756a2e63

I can even create a contract on one of the forms passed into a macro! This contract states that when using the send macro, the first expression (named the “receiver”) has to be a gobject. Let’s break it:

> (send 'foobar baz)
; receiver of send: broke its own contract
;   promised: gobject?
;   produced: 'foobar
;   in: gobject?
;   contract from:
;       <pkgs>/overscan/gstreamer/main.rkt
;   blaming: <pkgs>/overscan/gstreamer/main.rkt
;    (assuming the contract is correct)
;   at: stdin::7059-7066

When writing macros in Racket, syntax/parse am pretty cool.

928148c996fb22f5f8f6be76f5380ea0ed5a4bcb

This code creates inheritance in the style of Smalltalk. When I lookup a method, I try to find it on the GObject’s class, and if that fails, I try again on that class’s parent. Tail-call optimization, you’re the best.

5c788a69ba2ea6e9a5e1e2aafdadbc62a5c63c68

Another macro! Something that I missed from racket/class was something like Ruby’s respond_to?. Let’s duck type the heck out of some objects. I want to give myself tools and functionality so that within the context of a REPL I can learn as much as I can about the GObjects I get back from C. Bashing it with my keyboard. Functions like this new responds-to? macro and Contracts give me these training wheels.

f2dadfbe1b0b325e941dd2e7b08cc25b5461020a

Finally, I do something that I’m not 100% certain about. When I call a method on a gi-object (which my brain just substitutes for the word “class”), I look to see if that function is flagged as a constructor. If it is, I cast the returned object to this class. This is something that I’m doing just based on the experience I’m having with GStreamer.

For example, in my gstreamer/main.rkt, I call (pipeline 'new "my-pipeline"). I would think this would have returned me a GstPipeline instance, but Pipeline inherits from Element and the new function returns an Element. By doing the cast this way, I can ensure that I get back an instance of an object that I’ve called a constructor on. I might regret this decision, but so far based on my limited exposure to these C libraries, this feels like the right thing to do.

Now I can go through the GStreamer documentation and follow along.

> (pipeline-add-many my-pipeline source filter sink)
#t
> (send source link filter)
#t
> (send filter link sink)
#t

Now I’ve got myself a Gst pipeline that doesn’t do anything! I need to keep reading the documentation…

📃 Mark