Brandx, a new library for OOP, interfaces, generics, and more

I'm pleased to announce brandx, a new Racket library for OOP, interfaces, generics, components, and more. The docs are available online, with an introduction including multiple examples. (Thanks to Matthias for comments on earlier drafts of the docs.)

This library has two origin stories: Practically, I wanted better support for OO-style programming (dynamic dispatch, inheritance, etc) directly with structs (retaining struct contracts, destructuring with match, etc) that had better ergonomics (IMO) and contract support than racket/generics. Theoretically, I wanted to explore generalizations of the "mixin" concept. In the racket/class world, a mixin is a function from class to class. In a world where there is no unified "class" but a collection of implementations of different interfaces, perhaps a type implementation should look like a component graph, supporting both parent-child edges (inheritance) and sibling edges (components and traits). That's the model underneath brandx, although I expect most applications will only need the basic generics support.

Differences with other libraries

(Copied from the documentation section of the same name.)

Improvements over racket/generic: This library has better binding ergonomics: implementations may use export prefixes to avoid shadowing generic functions, and multiple interfaces may be implemented in a single shared definition scope. Contracts are associated with interface members, and interface imports and exports are contract boundaries. Calls to super-methods are supported. Abstraction in the style of mixins and traits is supported at the granularity of interfaces.

Limitations compared to racket/generic: This library's generic functions always dispatch on their first positional argument, and they do not support "defaults" (#:defaults and #:fast-defaults in define-generics); instead, define a wrapper function. Method redirection (as with redirect-generics, etc) is not supported.

Differences from racket/class: Users retain direct access to structs, including pattern-matching via match. All method names to be visible externally or visible to subclasses must be declared in an interface. There is no syntactic restriction or special treatment of methods; in particular, there is no implicit this variable. Consequently, there is no syntactic support for treating fields as variables, and there is no special treatment of calls on the same object. There is no special construction/initialization support. There is no support for final methods, abstract methods, or augment methods.

Experiences so far

I've tested and evolved the design and implementation of brandx with a rewrite of the crypto library, previously based on racket/class. It isn't done (about 75%), but it looks promising so far. The switch has encouraged me to use contracts on internal interfaces far more than I previously had.


The API is close to stable, but there's still a little time for minor changes. Questions and comments are welcome.

5 Likes