Authoring Fable bindings: inheritance and variance in action.

JavaScript cheats. If you want to pass some properties, there’s no need for a type to describe them – just return anything you want inside of curly braces and you are done. Of course there’s a cost, but that not what this post is about.

How does one achieve this level of freedom in order to integrate with JavaScript libraries out there when writing in a statically typed language – F#? Fable provides several mechanisms for transparent interop with the rest of JS world:

  • Record Types – going from typed to untyped is not a problem;
  • Discriminated unions with a special attribute [<KeyValueList>].

Currently a typical way to create new fable bindings for a library starts with TypeScript definitions imported via ts2fable.  The tool produces F# type definitions that closely resemble the OO roots of TypeScript definition, but are not particularly interesting to work with from a functional-first language. So the usual second step when authoring a binding is a creation of a “helper” DSL – conversion of generated interfaces to KeyValueList discriminated unions and addition of factory functions to take lists of DU instances that describe various properties of the target. Let’s take a look at an example, from React JSX intro:

Brief detour: JSX is template language that lets you mix React elements described in XML/HTML-like syntax with JS6, it’s transpiled later into plain old JS. So in lines 2-4 we see a construction of h1element with implicit child element (“hello…” text) and an explicit className and key attributes set. With regards to React, key is unnecessary and may even be invalid, but this post is not about React and the attribute is perfect to discuss the types involved.

We’ll skip the actual JS definition of the h1, just keep in mind that we are setting a couple of properties and let’s dive into TypeScript definition for each:

We can see that TypeScript introduced an interface hierarchy to describe something that would be a runtime composition in JS – object is just an array, indexed by property name. Leaving aside for now the interesting use of type intersection, let’s take a look at what F# binding we get from ts2fable:

Unsurprisingly we get a couple of interfaces Props and HTMLAttributes, unified in HTMLProp. Let us consider how we would use the interfaces like that… we’d need an object of type HTMLProp that we’d call an interface method on, but it’s only useful for callbacks. The callbacks are contravariant -as long as it receives the “biggest” type we are type-safe.

But how would we construct an object in type-safe manner using these definitions? The answer is we can’t. Constructors need a covariant list of properties.

Let’s see what the idiomatic F# DSL offers on top of the imported definitions:

Here we see the DUs representing the properties from the imported interfaces and the “factory” functions to facilitate assembly of the element objects. We also see a marker interface IProp that doesn’t exist anywhere in the previously seen hierarchy. Why do we need the marker interfaces? To answer that question, let’s see the usage:

We pass HTMLAttr.ClassName and Props.Key as the list elements and a child text element.

What is interesting is that here we have a typed list with instances coming from different types. In JS we just bundle anything we like with {} , in TypeScript we can do the same, but there are also “intersection types” that allow one to express mixing of various properties together, in F# we need the artificial marker interface to relate the unrelated types, so that our list could be constructed.

What we see taking place is transition from interface definitions usable by contravariant callbacks to a covariant list of properties that can be passed into the createElement and it required the unifying interface type to make it possible.

Unlike the fairly simple React hierarchy, which if you squint looks like a match for the original inheritance tree, ReactNative is more involving and requires a lot of unifying interfaces – one for each valid combination of properties for a given element and requires careful thought and consideration. It’s a work in progress. On the other hand what JS can only check at runtime, F# can check at compile time, as well as provide a nice IntelliSense experience!

Advertisements
Authoring Fable bindings: inheritance and variance in action.