Day 4
Let’s Make a Tree
Last day we have learned about basic building blocks of an Elm program:
value
,
name
and
type
. Today we are going to use this blocks to compose more complex structures. On the way we are going to learn about:
Recursion

The Problem

We want to have a tree that looks like this:
The tree is built from segments: a line and a dot. In this respect it is similar to the connected dots we created yesterday. If we group the dot and a line, we will have the building block for our tree. We can do it with
Svg.g
function (
g
is an abbreviation of “group”). Just like
Svg.svg
, it takes list of attributes and list of children. We can use it like that:
Elm
1 - 7
unfold
8
main =
9
[ Svg.g []
10
[ dot "skyblue" 0
11
, line "skyblue" 0
12
]
13
, Svg.g []
14
[ dot "orange" 72
15
, line "orange" 72
16
]
17
, Svg.g []
18
[ dot "red" 144
19
, line "red" 144
20
]
21
, Svg.g []
22
[ dot "lime" 216
23
, line "lime" 216
24
]
25
, Svg.g []
26
[ dot "maroon" 288
27
, line "maroon" 288
28
]
29
]
30 - 78
unfold
Press
COMPILE
on Ellie. There should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the
group
and
line
functions have the same arguments passed to them. This kind of repetition begs for a function. Let’s call it
segment
and define like this:
Elm
1 - 7
unfold
8
main =
9
[ segment "skyblue" 0
10
, segment "orange" 72
11
, segment "red" 144
12
, segment "lime" 216
13
, segment "maroon" 288
14
]
15
|> Svg.svg
16
[ Svg.Attributes.height "100%"
17
, Svg.Attributes.width "100%"
18
, Svg.Attributes.style "background: none"
19
, Svg.Attributes.viewBox "-100 -100 200 200"
20
]
21
|> Element.html
22
|> Element.layout
23
[ Element.width Element.fill
24
, Element.height Element.fill
25
]
26
27 - 62
unfold
63
64
segment color rotation =
65
Svg.g []
66
[ dot color rotation
67
, line color rotation
68
]
69
70
I hope you can see the pattern in what we are doing. We are taking repetitive blocks of code and turning them into named functions. Parameters help us deal with variability in the repeated code (like color and rotation that is different for each segment).
Click
COMPILE
. There should still be no visible difference in the behavior of the program, but our source code is getting more readable. That’s good.
The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child red segments and one green segment. Each red segment has two green child segments.
It’s similar to the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let’s do that! Change the definition of
segment
function as follows:
Elm
1 - 7
unfold
8
main =
9
[ segment "skyblue" 0
10
, segment "orange" 72
11
, segment "red" 144
12
, segment "lime" 216
13
, segment "maroon" 288
14
]
15
|> Svg.svg
16
[ Svg.Attributes.height "100%"
17
, Svg.Attributes.width "100%"
18
, Svg.Attributes.style "background: none"
19
, Svg.Attributes.viewBox "-100 -100 200 200"
20
]
21
|> Element.html
22
|> Element.layout
23
[ Element.width Element.fill
24
, Element.height Element.fill
25
]
26
27
28 - 62
unfold
63
64
segment color rotation =
65
Svg.g []
66
[ dot color rotation
67
, line color rotation
68
, Svg.g
69
[ Svg.Attributes.transform
70
(String.concat
71
[ "rotate("
72
, String.fromFloat rotation
73
, ") translate(80)"
74
]
75
)
76
]
77
[ dot "red" 15
78
, line "red" 15
79
, dot "blue" -15
80
, line "blue" -15
81
]
82
]
83
84
Now the program should display something like this:
It already starts to look interesting, but there are two problems with it. First it doesn’t fit on screen. This is easy to fix using
viewBox
. We need to zoom out to see more of a picture. Change the value passed to
viewBox
on line 19 to
"-500 -500 1000 1000"
, effectively zooming out 5x but keeping the origin in the center.
Then the lines look ugly displayed on top of the dots of different color. This is also easy to fix. In SVG several siblings (children of the same parent element) lay one on top of another. The “younger” sibling is laying on top of the older, so in our case the group containing the blue and red dots and lines lays on top of the dot and line. Let’s change the order of siblings by moving the group to the beginning of the list, like this:
Elm
1 - 7
unfold
8
main =
9
[ segment "skyblue" 0
10
, segment "orange" 72
11
, segment "red" 144
12
, segment "lime" 216
13
, segment "maroon" 288
14
]
15
|> Svg.svg
16
[ Svg.Attributes.height "100%"
17
, Svg.Attributes.width "100%"
18
, Svg.Attributes.style "background: none"
19
, Svg.Attributes.viewBox "-500 -500 1000 1000"
20
]
21 - 63
unfold
64
segment color rotation =
65
Svg.g []
66
[ Svg.g
67
[ Svg.Attributes.transform
68
(String.concat
69
[ "rotate("
70
, String.fromFloat rotation
71
, ") translate(80)"
72
]
73
)
74
]
75
[ dot "red" 15
76
, line "red" 15
77
, dot "blue" -15
78
, line "blue" -15
79
]
80
, dot color rotation
81
, line color rotation
82
]
83
84
85
With this two changes applied you should see something like this in the browser:
So now each segment has two sub segments: red and blue. The red one is rotated 15 degree clockwise, the blue one is rotated 15 degree counterclockwise. But they look the same. Our goal is to make them variable. This means that we need one more parameter to the segment: list of children. Let’s do it:
Elm
1 - 6
unfold
7
8
main =
9
[ segment "skyblue"
10
0
11
[ dot "red" 15
12
, line "red" 15
13
, dot "blue" -15
14
, line "blue" -15
15
]
16
, segment "orange" 72 []
17
, segment "red" 144 []
18
, segment "lime" 216 []
19
, segment "maroon" 288 []
20
]
21 - 32
unfold
33
34
segment color rotation children =
35
Svg.g []
36
[ Svg.g
37
[ Svg.Attributes.transform
38
(String.concat
39
[ "rotate("
40
, String.fromFloat rotation
41
, ") translate(80)"
42
]
43
)
44
]
45
children
46
, dot color rotation
47
, line color rotation
48
]
49
50 - 87
unfold
We have added third parameter to the
segment
function on line 34 (before the changes it was 64). The parameter is named
children
and we use it in place of the list on line 45 (previously 75). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for
children
parameter, so let’s just give each of them an empty list - meaning they have 0 children.
Once all changes are applied, click
COMPILE
and see that only first, skyblue segment has child segments.
Before we continue, let’s notice that on lines 11 - 15 we have a nice opportunity to remove some repetition. Consider that dot and a line with same rotation and color are just a segment. We can replace:
Elm
1 - 10
unfold
11
[ dot "red" 15
12
, line "red" 15
13
, dot "blue" -15
14
, line "blue" -15
15
]
16 - 88
unfold
with:
Elm
1 - 10
unfold
11
[ segment "red" 15 []
12
, segment "blue" -15 []
13
]
14 - 86
unfold
A segment within a segment! Consider that every segment can have child segments. This way we can build a tree as complex as we want. Try adding more children:
Elm
1 - 7
unfold
8
main =
9
[ segment "skyblue"
10
0
11
[ segment "red" 15 []
12
, segment "blue"
13
-15
14
[ segment "yellow" 45 []
15
, segment "pink" -20 []
16
, segment "green" -90 []
17
]
18
]
19
, segment "orange" 72 []
20
, segment "red" 144 []
21
, segment "lime" 216 []
22
, segment "maroon" 288 []
23
]
24
|> Svg.svg
25
[ Svg.Attributes.height "100%"
26
, Svg.Attributes.width "100%"
27
, Svg.Attributes.style "background: none"
28
, Svg.Attributes.viewBox "-500 -500 1000 1000"
29
]
30 - 90
unfold
Time to play
🌳 🌲 🌴 🎄
Try building your own tree. Play with colors and sizes.
Hint: if you add
Svg.attributes.strokeWidth "2"
attribute to the line, you will get thicker branches. Try different combinations of stroke width and radius of a dot.

Rules Based Tree

I hope you had fun and are proud of your tree. If you made a big one, perhaps you have noticed that all this nesting gets pretty tedious. What if we could make the computer do the hard work? We can do it, but as you may have noticed computers are not very smart, so we need to give them exact rules.
Let’s say we have three types of segments: brown, green and red. The child segments depend on the parent like this:
1.
We always start with a brown segment going up (-90 degree).
2.
The brown segment will have three green child segments:
one going straight (0 degrees),
one a little bit to the right (20 degree)
and one little bit to the left (-30 degree).
3.
Green segments will have three red child segments
one at -45 degree
one at -5 degree
one at 50 degree
The tree following these rules will look like this:
To represent this in code we will need to learn few new concepts:
dictionary
,
record
,
type alias
and
mapping over a list
.
Let’s start with a record. Currently our
segment
function takes three arguments first two are
color : String
and
rotation : Float
. The last one is a list of child segments. Instead we can merge the first two into one argument of type
{ color : String, rotation : Float }
. It will look like this:
Elm
1 - 7
unfold
8
main =
9
[ segment { color = "skyblue", rotation = 0 }
10
[ segment { color = "red", rotation = 15 } []
11
, segment { color = "blue", rotation = -15 }
12
[ segment { color = "yellow", rotation = 45 } []
13
, segment { color = "pink", rotation = -20 } []
14
, segment { color = "green", rotation = -90 } []
15
]
16
]
17
, segment { color = "orange", rotation = 72 } []
18
, segment { color = "red", rotation = 144 } []
19
, segment { color = "lime", rotation = 216 } []
20
, segment { color = "maroon", rotation = 288 } []
21
]
22 - 88
unfold
Not much changed. Instead of passing two separate values for
color
and
rotation
we pass one value with two named fields. That’s a record!
Why did I do it? That way I can store complete information about a segment in one value. I need this for the
dictionary
of rules.
Dictionary let’s us associate one value (let’s say color) with another value (let’s say a list of segments). You can have as many entries in your dictionary as you want. That’s perfect for storing our rules. We can start with an empty dictionary and then add our two rules: one for brown and one for green segments. We do it like this:
Elm
1
module Main exposing (main)
2
3
import Dict
4
import Element
5
import Svg exposing (Svg)
6
import Svg.Attributes
7
8 - 34
unfold
35
36
rules =
37
Dict.empty
38
|> Dict.insert "brown"
39
[ { color = "green", rotation = 0 }
40
, { color = "green", rotation = 20 }
41
, { color = "green", rotation = -30 }
42
]
43
|> Dict.insert "green"
44
[ { color = "red", rotation = -45 }
45
, { color = "red", rotation = -5 }
46
, { color = "red", rotation = 50 }
47
]
48 - 103
unfold
We have added an import statement for
Dict
module and created a dictionary with two entries: one for
"brown"
and one for
"green"
. The values associated with these keys are a list of segment records. The meaning of this is following:
Whenever a brown segment is created, also create three green children
Whenever a green segment is created, also create three red children
There is no rule for red segments, so they will have no children. Now we will apply the rules. We have to change the definition of
segment
function. It will no longer take the second argument - children will be added according to rules, not arbitrarily.
Here it is:
Elm
1 - 48
unfold
49
50
segment { color, rotation } =
51
Svg.g []
52
[ rules
53
|> Dict.get color
54
|> Maybe.withDefault []
55
|> List.map segment
56
|> Svg.g
57
[ Svg.Attributes.transform
58
(String.concat
59
[ "rotate("
60
, String.fromFloat rotation
61
, ") translate(80)"
62
]
63
)
64
]
65
, dot color rotation
66
, line color rotation
67
]
68
69 - 106
unfold
We also have to change the definition of
main
. Segment no longer takes two arguments, and its children are calculated according to rules, so we can just add one brown segment and rotate it upward (-90 degree)
Elm
1 - 7
unfold
8
9
main =
10
[ segment { color = "brown", rotation = -90 } ]
11
|> Svg.svg
12
[ Svg.Attributes.height "100%"
13
, Svg.Attributes.width "100%"
14
, Svg.Attributes.style "background: none"
15
, Svg.Attributes.viewBox "-500 -500 1000 1000"
16
]
17
|> Element.html
18
|> Element.layout
19
[ Element.width Element.fill
20
, Element.height Element.fill
21
]
22
23 - 94
unfold
The result should be exactly like we wanted it to be:
Nice, but what’s going on here:
Elm
1 - 36
unfold
37
38
segment { color, rotation } =
39
Svg.g []
40
[ rules
41
|> Dict.get color
42
|> Maybe.withDefault []
43
|> List.map segment
44
|> Svg.g
45
[ Svg.Attributes.transform
46
(String.concat
47
[ "rotate("
48
, String.fromFloat rotation
49
, ") translate(80)"
50
]
51
)
52
]
53
, dot color rotation
54
, line color rotation
55
]
56
57 - 94
unfold
There are three new things here:
1.
Dict.get
2.
Maybe.withDefault
3.
List.map
The first one is pretty simple. Previously we have been inserting entries into the dictionary. Each entry has a key (color of the segment) and value (list of child segments). Now we are getting these values back.
But what if the entry for a given key is not there? For example we have not inserted the entry for the
"red"
key? Well, then we get
Nothing
. But our
segment
function cannot work with nothing - it has to get a list of records with colors and rotations. So when we have nothing, we can’t call the
segment
function. To understand this, consider the following.
If there is an entry, we will get a list of records. We want a list of segments. Each of the records can be passed to the
segment
function to produce one segment. That’s where
List.map
comes in. Given a list and a function it will call the function with each of the elements in the list and give back the list of produced values. Let’s see a simpler example in the REPL:
Elm Repl
> fun something = something ++ " is fun!"
<function> : String -> String
> fun "Learning Elm"
"Learning Elm is fun!" : String
> fun "Tree"
"Tree is fun!" : String
> fun "Software"
"Software is fun!" : String
> things = [ "Elm", "Software", "Tree" ]
["Elm","Software","Tree"] : List String
> List.map fun things
["Elm is fun!","Software is fun!","Tree is fun!"] : List String
>
Take some time to grasp it and then compare with our code.
Elm
1 - 36
unfold
37
38
segment { color, rotation } =
39
Svg.g []
40
[ rules
41
|> Dict.get color
42
|> Maybe.withDefault []
43
|> List.map segment
44
|> Svg.g
45
[ Svg.Attributes.transform
46
(String.concat
47
[ "rotate("
48
, String.fromFloat rotation
49
, ") translate(80)"
50
]
51
)
52
]
53
, dot color rotation
54
, line color rotation
55
]
56 - 94
unfold
Let’s analyze it line by line:
1.
First line evaluates to a dictionary of rules.
2.
Second takes that dictionary and gets a list of records for a given color (it maybe nothing if there is no entry, for example in case of
"red"
).
3.
Third gives an empty list if the result of second line was nothing.
4.
Fourth maps this list of records with
segment
function, producing a list of segments (the result can be an empty list if the input was empty)
5.
Finally we pass the list to the
Svg.g
function, producing a single group (again, possibly empty).
It’s a lot to absorb, so take your time to think about it.

Towards Infinity

Our tree is very cool but rather simple. Wouldn’t it be nice if it could grow bigger and bigger? What if we could say that a brown segment produces two green (left and right) and a brown (straight)? Then the brown child would have two green and a brown too. But brown gives more brown
ad infinitum
(pardon my latin).
You can try that, but don’t expect to see anything interesting in the browser. Your computer is trying very hard to draw a tree for you, but after going through hundreds of thousands of segments’ generations it will give up, perhaps saying “too much recursion”. According to the rules you gave there is no stopping to this tree.
A funny fact about computers is that they generally can’t tell if the task you give them can be finished or not. It’s called
the halting problem
. Best they can do is give up after certain time. That’s what happens here.
So that won’t work. But it would be nice to have arbitrarily complex trees with repeating patterns - that’s how real trees grow. Since our problem is not ever stopping, maybe we can tell the program to simply stop after certain number of generations.
We can do it like this. When we create our first segment (the brown one), we will pass it a second argument (let’s call it
age
). It will be an integer. The age of a parent will be decremented and passed to all it’s children (so every child is younger than it’s parent by one generation). Eventually the program will reach segments with
age
being
0
. This segment will have no children, thus stopping the whole process. Here is how we can implement it:
Elm
1 - 8
unfold
9
main =
10
[ segment 4 { color = "brown", rotation = -90 } ]
11
|> Svg.svg
12
[ Svg.Attributes.height "100%"
13
, Svg.Attributes.width "100%"
14
, Svg.Attributes.style "background: none"
15
, Svg.Attributes.viewBox "-500 -500 1000 1000"
16
]
17
|> Element.html
18
|> Element.layout
19
[ Element.width Element.fill
20
, Element.height Element.fill
21
]
22 - 37
unfold
38
segment age { color, rotation } =
39
if age <= 0 then
40
Svg.g [] []
41
42
else
43
Svg.g []
44
[ rules
45
|> Dict.get color
46
|> Maybe.withDefault []
47
|> List.map (segment (age - 1))
48
|> Svg.g
49
[ Svg.Attributes.transform
50
(String.concat
51
[ "rotate("
52
, String.fromFloat rotation
53
, ") translate(80)"
54
]
55
)
56
]
57
, dot color rotation
58
, line color rotation
59
]
60 - 98
unfold
On line 10 we pass the age to the
segment
function as the first argument. The record is a second parameter now. The reason is that it makes mapping on line 47 easier. Age is the same for all the children (age of the parent minus 1).
On line 39 we see something new. It’s the
if ... then ... else ...
expression. You can probably guess how it works: first we give it a condition (in this case
age <= 0
- meaning that age is zero or less). That’s our halting condition.
In the
then
block we put the value to produce if the condition is met (here an empty SVG group). This will effectively stop the growth of the tree - no more children from this segment.
The
else
block contains the value to be produced when the condition is not met. In our case it means that the segment is old enough to have children.
The
else
value is almost the same as the whole
segment
function before the change. The only difference is that on line 47 we pass the
age - 1
to the
segment
function used in
List.map
.
The result should be like this:
Go ahead and play with the rules and age.
😺 🎾
It’s safe - the tree will always stop growing after a given number of generations.

Every Branch was Once a Twig

I hope you had fun playing with the tree. You can make it really interesting and complicated now. But it doesn’t look very much like a real, organic tree. One problem is that with a real tree old branches are thick and long, while young are thin and short.
Since the size depends on the age, and the age is given for every segment, we can fix that relatively easy. Let’s start by making the dots smaller or bigger depending on the age of the segment, like this:
Elm
1 - 37
unfold
38
segment age { color, rotation } =
39
if age <= 0 then
40
Svg.g [] []
41
42
else
43
Svg.g []
44
[ rules
45
|> Dict.get color
46
|> Maybe.withDefault []
47
|> List.map (segment (age - 1))
48
|> Svg.g
49
[ Svg.Attributes.transform
50
(String.concat
51
[ "rotate("
52
, String.fromFloat rotation
53
, ") translate(80)"
54
]
55
)
56
]
57
, dot age color rotation
58
, line color rotation
59
]
60
61
62
dot age color rotation =
63
Svg.circle
64
[ Svg.Attributes.r (String.fromFloat age)
65
, Svg.Attributes.cx "0"
66
, Svg.Attributes.cy "0"
67
, Svg.Attributes.fill color
68
, Svg.Attributes.transform
69
(String.concat
70
[ "rotate("
71
, String.fromFloat rotation
72
, ") translate(80)"
73
]
74
)
75
]
76
[]
77 - 98
unfold
Next let’s pass the age to the
line
function and use it for both the thickness (
strokeWidth
) and length (
x2
).
Elm
1 - 36
unfold
37
38
segment age { color, rotation } =
39
if age <= 0 then
40
Svg.g [] []
41
42
else
43
Svg.g []
44
[ rules
45
|> Dict.get color
46
|> Maybe.withDefault []
47
|> List.map (segment (age - 1))
48
|> Svg.g
49
[ Svg.Attributes.transform
50
("rotate("
51
++ String.fromFloat rotation
52
++ ") translate(80)"
53
)
54
]
55
, dot age color rotation
56
, line age color rotation
57
]
58
59 - 73
unfold
74
75
line age color rotation =
76
Svg.line
77
[ Svg.Attributes.strokeWidth (String.fromFloat age)
78
, Svg.Attributes.x1 "0"
79
, Svg.Attributes.y1 "0"
80
, Svg.Attributes.x1 (String.fromFloat (age * 10))
81
, Svg.Attributes.y1 "0"
82
, Svg.Attributes.stroke color
83
, Svg.Attributes.transform
84
( "rotate("
85
++ String.fromFloat rotation
86
++ ")"
87
)
88
]
89
[]
90
91
If you click
COMPILE
now, the tree will look as if it exploded. It’s funny. The reason is that we did not adjust the translation of the child groups nor the dots. It’s always 80, even though the length of the lines is variable - see lines
53
and
72
. Let’s change it like that:
Elm
1 - 37
unfold
38
segment age { color, rotation } =
39
if age <= 0 then
40
Svg.g [] []
41
42
else
43
Svg.g []
44
[ rules
45
|> Dict.get color
46
|> Maybe.withDefault []
47
|> List.map (segment (age - 1))
48
|> Svg.g
49
[ Svg.Attributes.transform
50
("rotate("
51
++ String.fromFloat rotation
52
++ ") translate("
53
++ String.fromFloat (age * 10)
54
++ ")"
55
)
56
]
57
, dot age color rotation
58
, line age color rotation
59
]
60
61
62
dot age color rotation =
63
Svg.circle
64
[ Svg.Attributes.r (String.fromFloat age)
65
, Svg.Attributes.cx "0"
66
, Svg.Attributes.cy "0"
67
, Svg.Attributes.fill color
68
, Svg.Attributes.transform
69
("rotate("
70
++ String.fromFloat rotation
71
++ ") translate("
72
++ String.fromFloat (age * 10)
73
++ ")"
74
)
75
]
76
[]
77
78 - 96
unfold
Now the tree should look correctly, like this:
It doesn’t look as lush as the one above. That’s because the rules and age are different. I will leave it to you to play with the rules. Make your tree beautiful - tomorrow we will make it grow!
Congratulations!
You are ready for
the final day
!