-
Notifications
You must be signed in to change notification settings - Fork 15
7ML7W Elm Day 1 Handling the Basics
After a brief moment of panic involving our traditional club bread knife going missing, we kicked off our first meeting on the third language of our current book: Elm.
While tucking into topic-appropriate Twiglets, we began by discussing how we wanted to run the meeting given that we'd had success previously by concentrating on a particular aspect of the chapter rather than going through the whole thing. As we had no particular area on which to concentrate this time around (and as the first day of each language tends to be a more generic "let's get started"), we decided to go through the chapter collectively led by @tuzz. Though he wasn't able to attend we kept @h-lame's notes on the chapter close to hand for discussion.
The chapter began by describing Elm as "strongly influenced by the ML language family" and we wondered what that actually meant: what are the defining characteristics of an ML language?
We looked to resident ML-enthusiast @tomstuart and contrasted Elm with languages such as Ruby, Perl and even JavaScript which have a more C-like background. We discussed whether characteristics might include algebraic data types and pattern matching or whether the focus on preventing run-time errors was somehow key but there was no clear consensus. Anecdotally, we heard about Elm evangelists being particularly proud of having no run-time errors in their production systems.
We took a brief moment to discuss a problem a lot of us had run into: the book uses Elm 0.15 but the current version of Elm is 0.18. What's worse is that Elm 0.16 introduced some major changes to the language including features covered in detail by the book itself. We decided to stick with the book's use of 0.15 for now but acknowledged this may be problematic in future.
As we didn't have someone driving a laptop at first, we attempted to use Try Elm and Ellie as an Elm REPL but, alas, both use a newer version of Elm and don't offer a way to switch versions.
Undetered, we plowed on with the chapter.
Our first introduction to Elm is through its REPL and the venerable number 4. The book told us to expect the following:
> 4
4 : number
However, it didn't mention that there is a good 30 second pause between entering the number 4
and it responding. @lazyatom mentioned that, as he runs Little Snitch on his laptop, he noticed Elm making a large number of network requests when he tried this himself. This fit with @wjlroe's suggestion on Slack that Elm lazily downloads core libraries in the REPL as needed.
Thank goodness we didn't try
5
!
After noting that using the string "String"
as an example of a string might have been needlessly confusing...
> "String"
"String" : String
We discussed Elm's notion of "strong typing" with the example of concatenation, particularly in contrast to the typical behaviour in JavaScript.
e.g. in JavaScript:
> 4 + "4"
'44'
And in Elm:
> 4 ++ "4"
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
The left argument of (++) is causing a type mismatch.
3| 4 ++ "4"
^
(++) is expecting the left argument to be a:
appendable
But the left argument is:
number
We discussed the quality of Elm's error messages and how it was an early proponent of good, human-readable error messages (with an obligatory mention of Rust's).
While discussing concatenation, we were introduced to Elm's concept of "type classes" such as appendable
. We read that concatenation with ++
work on appendable
data types but this was a little confusing as different appendable
s cannot be mixed:
> "Foo" ++ "bar"
"Foobar" : String
> [1, 2] ++ [3, 4]
[1,2,3,4] : List number
> "Foo" ++ [3, 4]
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
The right side of (++) is causing a type mismatch.
3| "Foo" ++ [3, 4]
^^^^^^
(++) is expecting the right side to be a:
String
But the right side is:
List number
After getting into quite a mess trying to understand the following code and its utterly baffling type signature (noting that it doesn't work in the REPL), we decided to skip onto Conditionals:
> a = [1, 2, 3, 4]
> a[1] = 2
<function> : List number -> number'
if | x < 0 -> "too small" \
| x > 0 -> "too big" \
| otherwise -> "just right"
We met our first if
statement in Elm and discussed the use of |
characters for breaking up clauses and whether or not whitespace is significant. @lazyatom noted that new lines are not required in an if
statement and therefore the above example also works like so:
if | x < 0 -> "too small" | x > 0 -> "too big" | otherwise -> "just right"
Which isn't too shabby at all.
We discussed the significance of otherwise
and it was noted that otherwise
is simply an alias for True
in Haskell much like the usage of the truthy :else
in Clojure's cond
function.
The book also gives an example of a simpler if
:
if x < 0 then "too small" else "ok"
However, we heard that if
statements have been somewhat revamped in more recent versions of Elm to make these two forms more consistent.
We then saw our first bit of pattern matching:
case list of \
head::tail -> tail \
[] -> []
We went down a bit of a rabbit hole discussing how head::tail
might work but, in the end, were content to note that the left-hand side of each case is some sort of pattern (e.g. like a regular expression) to both match and destructure any input. In this case, we're taking a list
and splitting off its head
and tail
and returning only its tail
.
As list
might have neither a head
nor tail
, we have to also provide the empty list []
as a case for our pattern match.
The next part of the chapter caused the most consternation as we saw how to construct our own algebraic data types:
type Color = Black | White
type Piece = Pawn | Knight | Bishop | Rook | Queen | King
type ChessPiece = CP Color Piece
Our immediate question was why the definition of ChessPiece
used CP
and what on earth is Color
or Black
anyway?
We broke this down a bit to discover that Black
is a data constructor for values of the type Color
and that CP
constructs ChessPiece
s but takes two arguments: a value of type Color
and a value of type Piece
.
Still, some of us were angry: could the constructor for ChessPiece
be called... ChessPiece
?
> type ChessPiece = ChessPiece Color Piece
> ChessPiece Black Knight
ChessPiece Black Knight : Repl.ChessPiece
Yes, it can! In general, it was very hard to keep track of whether we were in the language of types or the language of values.
Then came the most confusing example in the chapter:
type List = Nil | Cons Int List
The explanation for this line of code was as follows:
We define a type of
List
as either:
- The type
Nil
, or- A list constructed with a head of type
Int
and a tail of typeList
This immediately enraged several attendees (particularly those fond of Haskell) as, well, is Nil
a type?
WRONG.
We slowly realised that the explanation was extremely confusing as there is nothing inherently list-specific about any of the types in the given definition of List
: there is no special meaning to Nil
and no special meaning to Cons
.
In fact, while this seems very list like:
> type List = Nil | Cons Int List
> Cons 1 (Cons 2 (Cons 3 Nil))
Cons 1 (Cons 2 (Cons 3 Nil)) : Repl.List
It also works if you replace the names:
> type Wibble = Flibbertigibbet | Blancmange Int Wibble
> Blancmange 2 (Blancmange 3 (Blancmange 4 Flibbertigibbet))
Blancmange 2 (Blancmange 3 (Blancmange 4 Flibbertigibbet)) : Repl.Wibble
Sick of having Wibble
sList
s of only Int
s? Fret not, we also saw a unary type constructor for new types of List
s:
type List a = Nil | Cons a (List a)
This allows us to create List
s containing values of any type we specify:
> Cons "Foo" (Cons "Bar" Nil)
Cons "Foo" (Cons "Bar" Nil) : Repl.List String
Skipping over the first paragraph of this section, we encountered Elm's record type, much like the one we'd seen in "Types and Programming Languages":
{ color = Black, piece = Queen }
While these were pitched as an alternative to explicitly defining algebraic data types as we'd seen previously, @mudge wondered whether we could define our own type using a record:
> type ChessPiece = { color: Color, piece: Piece }
-- SYNTAX PROBLEM -------------------------------------------- repl-temp-000.elm
I ran into something unexpected when parsing your code!
2| type ChessPiece = { color: Color, piece: Piece }
^
I am looking for one of the following things:
a constructor for a union type
whitespace
Alas, we could not (after the meeting, @marksweston discovered that you can do this with type aliases).
We then discussed how to read the fields of a record:
> blackQueen = { color = Black, piece = Queen }
{ color = Black, piece = Queen } : { color : Repl.Color, piece : Repl.Piece }
> blackQueen.color
Black : Repl.Color
This was explained as syntactic sugar for the following:
> .color blackQueen
Black : Repl.Color
We were intrigued to see the definition of this scope-level .color
function:
> .color
<function> : { b | color : a } -> a
We touched on how Haskell has similar behaviour and how that leads to complications when you have multiple records with the same field names but potentially different types of values. This doesn't seem to be an issue in Elm however:
> pieceOfString = { length = "as long as you want" }
{ length = "as long as you want" } : { length : String }
> yearInDays = { length = 365 }
{ length = 365 } : { length : number }
> pieceOfString.length
"as long as you want" : String
> yearInDays.length
365 : number
What's more, it seems that any name preceded by a full stop desugars to a function to project a field out of a record, e.g. with no records in scope at all:
> .iwonder
<function> : { b | iwonder : a } -> a
> add x y = x + y
<function> : number -> number -> number
We enjoyed how simple Elm's function declarations are, chortled at a supposedly anonymous function named anonymousInc
and @jcoglan (now driving a REPL on his laptop) demonstrated how the above add
can be defined in several different but equivalent ways:
> add x = \y -> x + y
<function> : number -> number -> number
> add = \x y -> x + y
<function> : number -> number -> number
We marvelled at Elm's Elixir-like pipe operator:
> inc = add 1
<function> : number -> number
> 5 |> inc
6 : number
@mudge noted the similarity to Clojure's threading macros, e.g. ->
and we justified their existence as a more readable way to express heavily nested function calls.
e.g.
42 |> a |> b |> c |> d |> e
vs.
e(d(c(b(a(42)))))
We dug into the concept of currying (or, if you prefer, Schönfinkeling) and its distinction from "partial application". In brief, functions in Elm take no more than one argument (ala the Lambda calculus we've seen in previous books); the way functions appear to take multiple arguments is by returning successive functions, again only taking one argument. For example:
add x y = x + y
Is effectively equivalent to the following in JavaScript:
function add(x) {
return function (y) {
return x + y;
};
}
In Elm, we can pass either all arguments or just some of them:
> add 1
<function> : number -> number
In JavaScript:
> add(1)
[Function]
It's only when we pass both arguments that we get a result:
> add 1 2
3 : number
And
> add(1)(2)
3
The final topic we covered was the record subtyping used by Elm when inferring types passed to functions:
> somePoint = { x = 5, y = 4 }
{ x = 5, y = 4 } : { x : number, y : number1 }
> xDist point = abs point.x
<function> : { a | x : number } -> number
> xDist somePoint
5 : number
> xDist { x = 5, z = 100 }
5 : number
> xDist { y = 4 }
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
The argument to function `xDist` is causing a mismatch.
10| xDist { y = 4 }
^^^^^^^^^
Function `xDist` is expecting the argument to be:
{ a | x : ... }
But it is:
{ y : ... }
And with that, we were done!
- As may be clear from this write-up, we spent a lot of the time in the meeting being confused and frustrated by the examples given in the book and we wondered whether we should use other source material in future meetings to avoid this;
- One concern with going off-book more was the increased burden on attendees to prepare whereas we currently rely only on the given chapter;
- We were also concerned that the next two chapters cover the concept of functional reactive programming (FRP) using "signals" in Elm and this has been totally revamped in newer versions (replaced with the notion of "subscriptions")
- It was agreed that the meeting improved as soon as @jcoglan started trying things out in a REPL halfway through the meeting: we should ensure that someone can do this in future meetings from the beginning;
- We were thankful to @tomstuart for having organised the meeting and @marksweston kindly volunteered to organise the next.
Thank you to @leocassarani and Geckoboard for hosting the meeting and providing beverages, thanks to @tomstuart for organising the meeting, thanks to @tuzz for "all the bread" and for leading us through the chapter, to all those who contributed snacks and to @jcoglan for driving the REPL.
- Home
- Documentation
- Choosing a Topic
- Shows & Tells
- Miscellaneous
- Opt Art
- Reinforcement Learning: An Introduction
- 10 Technical Papers Every Programmer Should Read (At Least Twice)
- 7 More Languages in 7 Weeks
- Lua, Day 1: The Call to Adventure
- Lua, Day 2: Tables All the Way Down
- Lua, Day 3
- Factor, Day 1: Stack On, Stack Off
- Factor, Day 2: Painting the Fence
- Factor, Day 3: Balancing on a Boat
- Elm, Day 1: Handling the Basics
- Elm, Day 2: The Elm Architecture
- Elm, Day 3: The Elm Architecture
- Elixir, Day 1: Laying a Great Foundation
- Elixir, Day 2: Controlling Mutations
- Elixir, Day 3: Spawning and Respawning
- Julia, Day 1: Resistance Is Futile
- Julia, Day 2: Getting Assimilated
- Julia, Day 3: Become One With Julia
- Minikanren, Days 1-3
- Minikanren, Einstein's Puzzle
- Idris Days 1-2
- Types and Programming Languages
- Chapter 1: Introduction
- Chapter 2: Mathematical Preliminaries
- Chapter 3: Untyped Arithmetic Expressions
- Chapter 4: An ML Implementation of Arithmetic Expressions
- Chapter 5: The Untyped Lambda-Calculus
- Chapters 6 & 7: De Bruijn Indices and an ML Implementation of the Lambda-Calculus
- Chapter 8: Typed Arithmetic Expressions
- Chapter 9: The Simply-Typed Lambda Calculus
- Chapter 10: An ML Implementation of Simple Types
- Chapter 11: Simple Extensions
- Chapter 11 Redux: Simple Extensions
- Chapter 13: References
- Chapter 14: Exceptions
- Chapter 15: Subtyping – Part 1
- Chapter 15: Subtyping – Part 2
- Chapter 16: The Metatheory of Subtyping
- Chapter 16: Implementation
- Chapter 18: Case Study: Imperative Objects
- Chapter 19: Case Study: Featherweight Java
- The New Turing Omnibus
- Errata
- Chapter 11: Search Trees
- Chapter 8: Random Numbers
- Chapter 35: Sequential Sorting
- Chapter 58: Predicate Calculus
- Chapter 27: Perceptrons
- Chapter 9: Mathematical Research
- Chapter 16: Genetic Algorithms
- Chapter 37: Public Key Cryptography
- Chapter 6: Game Trees
- Chapter 5: Gödel's Theorem
- Chapter 34: Satisfiability (also featuring: Sentient)
- Chapter 44: Cellular Automata
- Chapter 47: Storing Images
- Chapter 12: Error-Correcting Codes
- Chapter 32: The Fast Fourier Transform
- Chapter 36: Neural Networks That Learn
- Chapter 41: NP-Completeness
- Chapter 55: Iteration and Recursion
- Chapter 19: Computer Vision
- Chapter 61: Searching Strings
- Chapter 66: Church's Thesis
- Chapter 52: Text Compression
- Chapter 22: Minimum spanning tree
- Chapter 64: Logic Programming
- Chapter 60: Computer Viruses
- Show & Tell
- Elements of Computing Systems
- Archived pages