Notes from a home rolled elm architecture test in Fable; one way to interface with javascript libraries from F#

I’ve been writing a bit more F# recently, and admittedly I haven’t been very satisfied with the available notes on interfacing to third party libraries. My main criterion in choosing F# over elm at any time would lie in whether most of the code would be home grown or the project would rely (or be able to rely) heavily on javascript libraries. I was also a bit curious about how I’d implement the guts of the elm architecture, given virtual-dom.

Obviously, “elm architecture in f#” yields a lot of hits from google, and everyone has their own take on it. It’s almost like writing a scheme compiler back in the early aughts. I’m writing about this exercise because I think my code serves a couple of purposes that haven’t been well served by other examples; it’s very short (and to my mind, probably close to minimal) and I think provides a pretty clear path from zero to a tiny fable program that uses foreign libraries, with a trivial build using browserify.

One thing that’s appealing about fable is that it has some of the same benefits as funscript (and was heavily inspired by it), and uses a very similar method of writing functions with inline javascript.

Fable however has a bit higher empty-source-file-to-hello-world ramp than funscript does. Fable runs as a standalone compiler which most people run via a configuration file, but also supports a more traditional command line mode that feels a bit more predictable IMO. Documentation online IMO doesn’t do a great job of just coming out and saying how to use fable this way.

It isn’t specified well in the documentation, but you can compile a 1-off fsx file using the projFile and outDir options on the command line without a fable config file.

Functions in fable output are sometimes curried, sometimes not. Specifically, exports from a module are not curried (all arguments are provided in the normal javascript style) and anonymous functions (including record members of function type) are. Record fields are generally stored as ordinary fields with the same name in a javascript object, so it’s possible to trivially pass records from and to javascript. Although the same guarantees aren’t provided by elm’s ports, the ability to hypothesize a record type in F# and know its structure in javascript gives a lot of options for calling javascript code. The way I chose was to describe the service I wanted to provide from javascript with a record of functions whose types are describable in F#. Interestingly, I was able to make the F# code functional and almost side-effect free. I chose baconjs as the signal system, so what I wrote hearkens back to elm-0.16 to a degree.

Most examples I’ve read using f# use AMD modules and requirejs, which I find unwieldy and complicated. I tend to prefer simplicity when deploying javascript (probably my newness showing), so I use browserify with commonjs style require calls when I need to combine stuff as I’m rather spoiled by npm/node’s way of doing things.

Since require seems a little wierd in f#, I chose to pass a struct of functions to my f# code upon importing and wrapping the foreign library from javascript. I may experiment with using require from f# later, but this provided a pretty clean way of separating f# and javascript code and allowed me to define f# types just for things that require them. fable has a dynamic operator, question-mark, which could also fit the bill, but seems to violate the spirit of writing f#. Ironically, the same facility in gopherjs doesn’t bother me nearly as much, because it’s formed around objects that are type sound in go.

In the code I wrote, I needed to pass just three functions to f#:

type ‘msg VDom =
{ vnode : string -> Property list -> Response list -> VNode list -> VNode;
vtext : string -> VNode;
post : ‘msg MsgStream -> ‘msg -> unit;
stream : ‘msg MsgStream;
}

Where VNode is an opaque type. Basically, just VNode construction as text or element and a method to post to a stream, given to the view function.

The main function of an f# TEA component returns this:

type (‘init, ‘msg, ‘state) Program =
{ init : ‘init -> ‘state;
update : ‘msg -> ‘state -> ‘state;
view : ‘state -> VNode
}

There is no explicit need for a side effect channel here (Cmd ‘msg in elm) since ML variants are not as pedantic as haskell descendants, so calls to post messages to streams would be at home here if the obvious extension of creating new bacon streams were provided. It’s certainly more convenient to be able to post inline to a stream with the immediate effect of an event being enqueued (compare to elm where send yields a side effect surrogate that must be consumed by the runtime), but I don’t yet know how much more error prone it might turn out to be.

The only thing about the main code that’s out of the ordinary is likely the view function. Note that the event response for “click” calls post on our event stream to post the response message on its own.

let view (vdom : Msg VDom.VDom) state =
let response =
fun evt -> (vdom.post vdom.stream (Text “Clicked!”))
in
(vdom.vnode
“button”
[{
name=”className”;
value=”testbutton”
}]
[{
name=”click”;
response=response
}]
[vdom.vtext
(String.concat
“”
[
Util.toString state.a; “ “; state.b
]
)
]
)

My code is here: https://github.com/prozacchiwawa/fable-bacon-vdom