Tiny Code Christmas - part 2: learning OCaml and that demos are mostly functional

This post is a follow on to the previous part 1 about Tiny Code Christmas 2022, and is about what I did for TCC 2023. In order to get more info on what TCC is and why it's a fun thing to do, I recommend you start there, but if you're just interested in me messing around with OCaml, then you're in the right place.

There were two things that motivated me to tackle this years TCC in the OCaml language. Firstly, a bunch of my colleagues at work use OCaml, and indeed work on language, and so there's been a bunch of encouragement from them that I should join in. However, I find I have two modes when it comes to working with programming languages: I can either work on a problem I'm not familiar with in a language I'm comfortable with, or I can do the inverse and tackle problems that I'm comfortable with in a language I'm having to pick up as I go, but I can't do both. Given my current work requires that I spend my time implementing ecological things and trying to do new things from a computer science perspective, I've just been leaning on my go to set of languages: Go, Python, and occasionally Swift.

In a parallel thread, a couple of months ago I was at the return of London BarCamp, and I happened to bump into Jonathan Hogg, someone with whom I used to share an office when I was doing my PhD. Jonathan was giving a talk, so I went to see what he was currently up to, and learned about Flitter, a purely functional programming language that he'd created to help him program visuals for live performances. I had a brief play with this after, as it seemed very cool, but a lack of free time eventually meant I didn't get far. But I liked the idea of having a declarative was to describe a light show rather than a long list of do this then that.

Thus it was when TCC 2023 was announced, and especially as the main challenges were mostly based on last years, it felt like a great chance to take OCaml, a mostly functional language that I don't know well, and apply it to the domain of programming visuals that I'm somewhat familiar with now, and do it in a declarative way as inspired by Jonathan's Flitter work.

For those who don't know either OCaml or are that familiar with functional programming, a very very brief primer. In most regular programming languages you come across, like C, Python, Lua (as used for last year's TCC), your program is basically a long list of "do this, then do that, and optionally do this thing". This is known in computer science terms as imperative programming. Each of these statements will typically either change the program's state or the state of the environment in which the code runs, and so slowly over time you build up your list of statements to sum up to some desired impact on the world.

Functional programming languages take a different approach, whereby it's much more like a expressing a series of mathematical equations that plug together to describe how state changes, and if you plug in your current state of the world into the equation, the result will be your new state. If you're not into this sort of thing, this perhaps sounds like a bit of a made up difference, but the point is that you're not telling the computer to do individual steps that will get you to the end result, rather you specify the maths for generating and end result from a starting position, and then the compiler does the turning that into low level steps the computer carries out, and this generally makes your changes in state explicit, where as in imperative languages a lot of the state is implicit, and thus easy to get wrong, and this is where you find bugs.

So in theory purely functional languages will make it easier to write safe software, but that comes at the expense they don't map to the mental model most people have of how to do tasks - the real world is naturally imperative: get the kettle, put water in it, turn it on, get the cup, etc. And indeed, some tasks in computing are imperative in nature too, and to express them functionally is awkward. Hence these days you get languages like Swift and OCaml that have a mix of functional and imperative behaviours - Swift leaning somewhat more on the imperative and OCaml more on the functional, but still both try to achieve that sweet spot of giving you the safety of a functional language, whilst the task composability of an imperative language. SwiftUI, Apple's new(ish) way of doing user interfaces is an attempt to make UI implementation more functional.

If you want to know more about OCaml's place in the world as a language, then I can recommend this podcast episode where Sabine Schmaltz, the maintainer of the ocaml.org website (hosted with OCaml of course), gives a good overview of the language, its influences, and how it compares to things like Go and Rust.

Rather than labour that point any more, lets look at how it went trying to solve Tiny Code Christmas in OCaml (all my code is on github, and I have a gallery of the outputs). Whilst I can lean on the imperative side of OCaml to keep things familiar, the idea is to try to follow the path inspired by Flitter and use the functional side of OCaml as much as I can. And indeed, it turns out that functional programming is quite a good model for a bunch of the effects I made in TCC.

To start with I was just trying to find my feet, both with OCaml and a way of getting pixels onto the screen. For the former I mixed doing with reading bits of Real World OCaml - it's a good book, but I'd failed to make headway with it before as I learn best by doing, and I found doing a challenge in some way, then reading a bit of the book to see how I could have used the language better, and doing better the next day, and repeating this worked really well for me.

For the pixel pushing I used the Graphics module, which gives a very simple way to plot pixels and basic shapes on the screen via X11. Whilst not the most advanced way of doing things, it being X11 meant that I can run my code on any of the computers I happen to be using, as macOS still has XQuartz support, and WSL under Windows supports X11 now too, and so I think I used both those and Linux directly to solve my challenges over the course of the month, which was nice. But beyond that, my code was very imperative to start with, as per the first couple of days:

Your browser does not support the video element.

Where you end up with sequences of statements to build the tree of primitive shapes

let draw_background () =
let horizon_height = (480 / 3) in
set_color white;
fill_rect 0 0 640 horizon_height;
set_color blue;
fill_rect 0 horizon_height 640 (480 - horizon_height)

...

let draw_scene (t: int) =
draw_background ();
draw_snow t 42 1;
draw_tree ((size_x ()) / 2) 60;
draw_snow t 62 2

This works, and got me started, but isn't really what I wanted to be doing. I did however put in place some TIC80-isms (TIC80 being the platform I used for last year's TCC), so my code from the start was built around the idea that you'd have a sort of implicit runloop behind the scenes like in TIC80 or even in say arduino programming, whereby you have two functions where you are called, one (which I called boot) that gets called just once at the start of execution, and then a second function (which I called tick) that gets called repeatedly, with a counter passed in as a time indicator.

let tick (t: int) =
draw_scene t

It's not doing much here, but later this is how we really end up with a functional programming style demo system. Obviously unlike in TIC80 and Arduino and things, I had to build the runloop myself, and so quickly I started trying to hide that code away into a library, so by the end of TCC, my main.ml really just had the demo code in it and nothing else - all the things I'd built on top of OCaml's graphics code were out of sight.

And what is "all the things I'd built" there? Well, my aim wasn't just to implement the TCC challenges directly, but to keep things relatable to the rest of the community that was using things like TIC80, I ended up building a fantasy console emulation layer over the course of the 21 challenges I did (there were 24 in total this year, the 12 from last year and then another 12 "extra" challenges for those who wanted to go beyond what was done last year). For instance, TIC80 keeps the idea of your video card having a fixed palette of 16 colours, and your demo code is drawing in that palette. So I wrote my own framebuffer abstraction that worked with a fixed palette that you define at the start of your program. This also gave me a place to add some scaling code so I was creating effects in low resolutions that befit retro computers and then scaling them up so they don't look tiny on modern displays.

I must confess, although I kept to the 16 colours (or fewer) of TIC80, I did alternative between the 240x136 resolution of TIC80 and 640x480 VGA resolution depending on the demo, as some just looked really good at the slightly higher pixel count, and I feel 640x480x16 still is a retro display in 2023 :)

Your browser does not support the video element.

If we look at the tick loop for the above example, you can perhaps see, if you squint a bit, that this is starting to be a lot less imperative and a lot more functional in style:

let tick (t : int) =
let height = size_y () and width = size_x () and ft = (float_of_int t) and colors = (List.length a_palette) in
let fcolors = float_of_int colors in
for j = 0 to height do
for i = 0 to width do
let x = float_of_int (i - (width / 2))
and y = float_of_int (j - (height / 2)) in
let d1 = (float_of_int width) /. sqrt ((x *. x) +. (y *. y) +. 1.0)
and c1 = ((atan2 y x) +. Float.pi) *. (fcolors /. (2.0 *. Float.pi)) in
let c2 = c1 +. (sin (ft /. 70.0) *. Float.pi *. 2.0)
and d2 = d1 +. (Float.rem (ft /. 10.0) fcolors) in
let p = (int_of_float (Float.floor c2)) lxor (int_of_float (Float.floor d2)) in
let pindex = (p mod colors) in
let color = List.nth a_palette (if pindex < 0 then (colors + pindex) else pindex) in
set_color color;
plot i j
done
done

This was before I added the framebuffer abstraction, so there's some imperative bits to do the actual drawing (you set the colour then you plot a point for example), but most of this code is just stacked mathematical equations and the value of each pixel only derives from the position on screen (the i, j loops) and the tick count (a proxy for time) - there is not other state happening here - a sort of perfect fit for functional programming.

If I look back at last year's solution for this in Lua, then the code is in that form anyway, and so I'd argue that this soft of demo coding is inherently functional, and thus not only was this an opportunistic way for me to learn OCaml, it was actually a very well aligned way too, which I'd not considered when I started.

function TIC()
for j=0,h-1 do
for i=0,w-1 do
x=i-(w/2)
y=j-(h/2)
d=400/math.sqrt((x*x)+(y*y)+1)
c=(math.atan2(y,x)+pi)*(16/(2*pi))
c=c+(math.sin(t/70)*pi*2)
d=d+((t/10)%16)
p=(d//1)~(c//1)
pix(i,j,(p&11)+8)
end
end
t=t+1
end

Indeed, by the time I'd completed TCC and moved onto Genuary (a generative art prompt per day for January), my entire program is now very functional in style for doing graphics effects:

open Claudius

let tick t s _prev =
let palsize = Palette.size (Screen.palette s) in
Framebuffer.init (Screen.dimensions s) (fun x y ->
let ft = (Float.of_int t) /. 10.
and fx = (Float.of_int x) /. 140.
and fy = (Float.of_int y) /. 140. in
let z = 10. +. (sin (ft /. 1000.) *. 5.)
and d = 10. +. (cos (ft /. 1000.) *. 5.) in
let fc = (sin (sin ((fx +. ft) /. z)) +. sin (sin ((fy +. ft) /. d))) *. Float.of_int(palsize / 2) in
let rc = ((int_of_float fc)) mod palsize in
if rc >= 0 then rc else (rc + palsize)
)

let () =
let screen = Screen.create 640 480 1 (Palette.generate_plasma_palette 1024) in
Tcc.run screen "Genuary Day 2: No Palette" None tick

Now there's no direct state changes happening in the code, rather you create a framebuffer with a function that is called for every pixel. Quite a few of the old-school raster effects do fit this pattern of only having each pixel depend on x, y, and t.

Some effects are more complicated, particular the vector or pseudo 3D effects, and do require a sort of imperative style "set up scene, do a transform, and then render to screen" flow, but because none of these stages rely on external state, they are still effectively functional just keyed to time, just at a slightly more macroscopic scale, as you can see in this loop:

Your browser does not support the video element.

let tick (t : int) (s : Tcc.screen) (_prev : Framebuffer.t) : Framebuffer.t =
let buffer = Framebuffer.init (Screen.dimensions s) (fun _x _y -> 15) in

let ft = Float.of_int t in

generate_torus ft
|> List.map (fun p ->
rotate_y (0.02 *. ft) p |> rotate_x (0.01 *. ft) |> rotate_z (0.005 *. ft)
)
|> List.sort point_z_cmp
|> render_to_primatives ft s
|> Framebuffer.render buffer;

buffer

The |> operator in OCaml just takes the output of the previous function and feeds it as the last output of the next function, letting you build up these pipelines which are sort of imperative, but because they're self contained equations are still functional.

And as another effect that looks stateful but turns out to be functional, here is one of my favourite effects of the set:

Your browser does not support the video element.

This looks like it's tracking the movement of a bunch of random particles, and so you'd expect some state to be there, but in fact whilst the points are randomly generated, they're done so from the same seed each tick, so you can recreate the world and move the based on the distance relative to the tick count you want, and so there is no state required.

let generat_points (count : int) (t : int) (screen : screen) : point list =
Random.init 42;
List.init count (fun index ->
{
x = ((Random.int screen.width) + (((index + 1) * t) / 20)) mod screen.width ;
y = ((Random.int screen.height) + (((index + 1) * t) / 20)) mod screen.height ;
}
)

For those wondering, the index is added to give them all different speeds, a neat trick provided by the TCC challenge that day. Ultimately give this function a point in time and it'll recreate the world from nothing consistently. This relates to something Jonathan said about his aims for Flitter:

My fave thing of purely functional graphical systems is being able to play with the clock: stopping it, running it backwards, skipping, etc.

Not only does the lack of state make things easier to reason about in terms of testing your code, but it also unlocks these kinds of creative flows that would be more complicated otherwise.

So, as a way of learning a new language, TCC is a pretty good route. I wrote a little bit of OCaml every day for the better part of a month, and slowly my code got more idiomatic thanks to the guidance of my colleagues like the ever patient Patrick. I was pleased to see that Jonathan also joined in TCC in the end with Flitter, to give another take on how functional some of these are, given Flitter is a must more pure functional language that OCaml (an advantage of being domain specific).

Do I know OCaml inside out yet? Certainly not, but I feel I've got enough familiarity that I might try using it in lieu of Go for a few projects this year. I know that I'm not yet an OCaml natural, as I stick to the comfort of explicit type declarations rather than letting the compiler use type inference for example, but I do have colleagues to help me get over that in the coming year.

I'm going to use the follow on challenge of Genuary, a generative art prompt a day for January, to try take my OCaml fantasy retro computer library to a sort of completed point so that I can draw a line under it and feel I have a suitable conclusion, and thing should anyone else want to try TCC in OCaml in the future can pick up and use without having to worry about both suitable abstractions, graphics libraries, and doing TCC in a language not many use.

I do want to give a quick shout out once again to the LoveByte community - not only is TCC a nicely rounded set of challenges that make for a great little puzzle a day, both the organisers and discord we very welcoming to the idea of someone making their own thing of it rather than just using one of the traditional retro/fantasy platforms. At no point did anyone object to OCaml being thrown into the usual mix of Lua solutions - it was accepted as a fun variation, and my solutions (all of which are up on my personal blog here) made it into the end of TCC live stream, which was nice. A great community of people interested in learning and helping others have fun.