Elm – fun with L-System (Part 1)

Fri­day was my last day at Gamesys, bring­ing to an end a won­der­ful 6 years I’ve enjoyed at the com­pa­ny. See­ing as I’m start­ing my new job at JustEat in Novem­ber, so for the first time in almost 10 years I have time on my hands to relax and go through my back­log of TODOs.

One of those out­stand­ing TODOs was to check out L-Sys­tem, which I’ve been fas­ci­nat­ed about ever since I read this post by Math­ias Bran­dewinder.

 

I want­ed to try out the same exam­ples in Elm, which is anoth­er hob­by of mine.

Why?

Because it has this awe­some time-trav­el­ling debug­ger, which I can use to see how the dif­fer­ent exam­ples grow frame by frame and then even go back and forth!

 

Series:

  1. Algae (this)
  2. Pythago­ras Tree
  3. Can­tor Dust
  4. Koch Curve
  5. Sier­pin­s­ki Tri­an­gle
  6. Drag­on Curve
  7. Frac­tal Plant

 

As the wikipedia page for L-Sys­tem con­tains quite a few dif­fer­ent exam­ples, I’ll go through the imple­men­ta­tion for each in Elm over a few posts.

To start nice and easy, let’s look at the Algae exam­ple.

 

Modelling a L-System

Since there’s no draw­ing involved, this exam­ple allows us to focus on mod­el­ling a L-Sys­tem with types.

image

As you can see, my approach here is almost iden­ti­cal to Mathias’s, with the excep­tion that Sym­bol is a type alias for Char rather than a union type of its own right.

Why?

Most­ly because you can’t use a union type as key in a Dict in Elm. But also because I find it sim­pli­fies con­sum­ing code in this case, whilst a type alias still allows me to sur­face my domain through types.

Per­son­al­ly I’ve used both approach­es and make my choice based on the chance of get­ting it wrong (or, what addi­tion­al type safe­ty buys me) and the amount of effort involved.

p.s. Scott Wlashcin wrote a nice piece about the 2 approach­es from a per­for­mance point-of-view, which I also rec­om­mend you read­ing.

 

Then we just need a way to “evolve” a state based on the rules of a L-Sys­tem:

image

Since Dict.get returns a Maybe,  we have to pro­vide a default val­ue – the sym­bol itself in this case – if the sym­bol is not found in the rules.

To evolve the L-Sys­tem to a giv­en gen­er­a­tion, we can just use sim­ple recur­sion:

image

 

Modelling the Signals

For the demos, I want to be able to go from gen­er­a­tion to gen­er­a­tion using the arrows keys.

e.g. start­ing with gen 0, -> moves to gen 1, -> again moves to gen 2. But if you press <- then you move back to gen 1, and so on.

To do that, we need to cap­ture the arrow key press­es, and use the x val­ue (see map­ping below) to “change” the cur­rent gen­er­a­tion.

image

Start­ing with gen 0 (the axiom), we’ll fold over the sig­nal of inte­ger val­ues (either -1 for LEFT, or +1 for RIGHT), whilst cap­ping the out­put val­ue at 0 (hence the max 0 at the end).

image

Final­ly, we’ll map this sig­nal of gen­er­a­tion num­bers into a sig­nal of states by evolv­ing the L-Sys­tem to the spec­i­fied gen­er­a­tion:

image

 

All the code we’ve seen above are put inside a Core mod­ule that all the oth­er exam­ples will share.

 

Example 1 : Algae

So now we can final­ly look at one of the L-Sys­tem exam­ples. Once the Core mod­ule is in place, there’s actu­al­ly not much to be done here.

We still need to define the L-Sys­tem for Algae:

image

but since there’s noth­ing to be drawn on the screen, we’ll just keep things sim­ple and dis­play the out­put (list of Char) as a string instead:

image

 

Live Demo (here)

Remem­ber, use LEFT and RIGHT arrow keys to evolve/devolve the L-Sys­tem.

 

Source Code (here)

 

Next : Pythago­ras Tree

 

Links