git.iko.soy/lynxjs-elm/compiler
on master help sign in
lynxjs-elm/compiler/hints/recursive-alias.md
162 lines 4.4 kB history raw
2# Hints for Recursive Type Aliases
4At the root of this issue is the distinction between a `type` and a `type alias`.
7## What is a type alias?
9When you create a type alias, you are just creating a shorthand to refer to an existing type. So when you say the following:
11```elm
12type alias Time = Float
14type alias Degree = Float
16type alias Weight = Float
17```
19You have not created any *new* types, you just made some alternate names for `Float`. You can write down things like this and it'll work fine:
21```elm
22add : Time -> Degree -> Weight
23add time degree =
24 time + degree
25```
27This is kind of a weird way to use type aliases though. The typical usage would be for records, where you do not want to write out the whole thing every time. Stuff like this:
29```elm
30type alias Person =
31 { name : String
32 , age : Int
33 , height : Float
34 }
35```
37It is much easier to write down `Person` in a type, and then it will just expand out to the underlying type when the compiler checks the program.
40## Recursive type aliases?
42Okay, so let's say you have some type that may contain itself. In Elm, a common example of this is a comment that might have subcomments:
44```elm
45type alias Comment =
46 { message : String
47 , upvotes : Int
48 , downvotes : Int
49 , responses : List Comment
50 }
51```
53Now remember that type *aliases* are just alternate names for the real type. So to make `Comment` into a concrete type, the compiler would start expanding it out.
55```elm
56 { message : String
57 , upvotes : Int
58 , downvotes : Int
59 , responses :
60 List
61 { message : String
62 , upvotes : Int
63 , downvotes : Int
64 , responses :
65 List
66 { message : String
67 , upvotes : Int
68 , downvotes : Int
69 , responses : List ...
70 }
71 }
72 }
73```
75The compiler cannot deal with values like this. It would just keep expanding forever.
78## Recursive types!
80In cases where you want a recursive type, you need to actually create a brand new type. This is what the `type` keyword is for. A simple example of this can be seen when defining a linked list:
82```elm
83type List
84 = Empty
85 | Node Int List
86```
88No matter what, the type of `Node n xs` is going to be `List`. There is no expansion to be done. This means you can represent recursive structures with types that do not explode into infinity.
90So let's return to wanting to represent a `Comment` that may have responses. There are a couple ways to do this:
93### Obvious, but kind of annoying
95```elm
96type Comment =
97 Comment
98 { message : String
99 , upvotes : Int
100 , downvotes : Int
101 , responses : List Comment
102 }
103```
105Now let's say you want to register an upvote on a comment:
107```elm
108upvote : Comment -> Comment
109upvote (Comment comment) =
110 Comment { comment | upvotes = 1 + comment.upvotes }
111```
113It is kind of annoying that we now have to unwrap and wrap the record to do anything with it.
116### Less obvious, but nicer
118```elm
119type alias Comment =
120 { message : String
121 , upvotes : Int
122 , downvotes : Int
123 , responses : Responses
124 }
126type Responses = Responses (List Comment)
127```
129In this world, we introduce the `Responses` type to capture the recursion, but `Comment` is still an alias for a record. This means the `upvote` function looks nice again:
131```elm
132upvote : Comment -> Comment
133upvote comment =
134 { comment | upvotes = 1 + comment.upvotes }
135```
137So rather than having to unwrap a `Comment` to do *anything* to it, you only have to do some unwrapping in the cases where you are doing something recursive. In practice, this means you will do less unwrapping which is nice.
140## Mutually recursive type aliases
142It is also possible to build type aliases that are *mutually* recursive. That might be something like this:
144```elm
145type alias Comment =
146 { message : String
147 , upvotes : Int
148 , downvotes : Int
149 , responses : Responses
150 }
152type alias Responses =
153 { sortBy : SortBy
154 , responses : List Comment
155 }
157type SortBy = Time | Score | MostResponses
158```
160When you try to expand `Comment` you have to expand `Responses` which needs to expand `Comment` which needs to expand `Responses`, etc.
162So this is just a fancy case of a self-recursive type alias. The solution is the same. Somewhere in that cycle, you need to define an actual `type` to end the infinite expansion.