Learn carML in 5 years

I started working on carML on 19-MAR-2016, and much has changed since that time. I thought it would be fun to recap what I’ve done and where it has been since that time.

What is it?

carML is a tiny ML-dialect that I’ve been sorta poking around on for the last 5 years; it is meant to be:

I use it sometimes for client work, esp with the new Golang support, and it’s been interesting. I’ve been really lazy about adding things as well; I know it’s not really a compiler currently, I mostly code things carefully to avoid the edge cases I know exist, &c and so on.

Even with that, I’ve noticed simple composability with |>, match forms, and nicer types make it nicer to program in than C or Golang directly for certain things. The ability to express things with a match is quite nice, esp for pattern matching.

Being brave enough to release something you care about

One other note that is important (to me): carML is the first time that I released a language that I’m working on in real time. This isn’t even the first time I’ve written a language with similar syntax: the first time I toyed with this sort of thing was around 2003:

OCaml to Scala

Originally, carML used very OCaml-y types. This lead to very long type signatures like: ref of ref of array of int, which of course makes sense but is difficult to parse. The other side of it was that I originally made the latter part optional; I wanted to support things like ref when a user just wanted to use a pointer of some type. However, this lead to lots of parsing code, and made a mess of things. Eventually, I gave in and moved to a fairly clean Scala-style for types, enclosed with []. So the above becomes ref[ref[array[int]]], and is relatively easy to parse.

The other side of this was I wanted to clearly distinguish user types from system types. Tulip does this with .tag, but I decided to go with Tag style. This would mean that all user types and constructors need to be capitalized, but it saved on any confusion we may have as to what the next form needed to be: we either have an array literal (such as Either[int string]) or we have another form, and the type is finished.

Despite these things, the language is still fairly ML-y; I’m working on modules and row typing to make things a little nicer, and that should help too.

Standard ML accessors… for everything

Sometimes I have epiphanies; one day I was thinking about something deech said about tuples and product types. I realized that there’s not really much of a difference between:

Save for how we define & access them, but there needn’t be. I started to look into this, and realized that Standard ML already does this. There is a nice comparison by Danny Gratzer that covers it. Still, I’m keeping Scheme roots, and working with the usual combination of

as well as drawing lots of insporation from the various “Successor ML” dialects floating around, like Morel.

a whole new world…

Oh! and one other interesting thing is that carML supports both C and Golang, in human readable form:

>>> def foo x:int y:int => int = (sum x y)
(define foo (parameter-list (parameter-definition (identifier x) (type integer)) (parameter-definition (identifier y) (type integer)))
    (returns (type integer))
    (call (identifier sum) (identifier x) (identifier y)))
>>> %c
[!] C generation is: on
>>> def foo x:int y:int => int = (sum x y)
int
foo(int x, int y){
    return x + y;
}
>>> %c
[!] C generation is: off
>>> %g
[!] Golang generation is: on
>>> def foo x:int y:int => int = (sum x y)
func foo(x int, y int) int {
    return x + y
}

This means that I can define data structures in C or Golang, and have reasonable, human-readable code for both. Eventually I’d like to add JavaScript and WASM, but for now there is decent output for two languages I use often.

The next five years to come

There’s still a lot of TODOs for what I want to achieve; next up after I fix self-TCO, I’ll focus on adding some nice modules and top-level functions, as well as a real compiler.

More than enough for five more years.