Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add record types concept and associated exercise #369

Merged
merged 26 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a9b56e3
Add config.json for record types concept
ceddlyburge May 22, 2021
844a8dd
Add about.md for records concept
ceddlyburge May 24, 2021
34d8339
Add introduction for record concept
ceddlyburge May 24, 2021
7880101
Add links for record concept
ceddlyburge May 24, 2021
017a2aa
Add hints for records concept exercise
ceddlyburge May 25, 2021
b94d19f
Review records about.md
mpizenberg May 25, 2021
48e1e3f
Add config for records concept and exercise
ceddlyburge May 25, 2021
ead7daa
Fix typo
ceddlyburge May 25, 2021
34fc67f
Add introduction for records concept exercise
ceddlyburge May 25, 2021
18a230b
Add design.md for records concept exercise
ceddlyburge May 25, 2021
a639b69
Add code and docs for records concept exercise
ceddlyburge May 27, 2021
002995d
Fix location of test code
ceddlyburge May 27, 2021
42383bf
Add location of solution file
ceddlyburge May 27, 2021
c5b319f
Rename tests folder in order to change case
ceddlyburge May 27, 2021
145489f
Use lower case tests folder name in config
ceddlyburge May 27, 2021
983b3ea
Add records concept and exercise to config
ceddlyburge May 27, 2021
9bd0a3f
Fix code typo in about.md
mpizenberg Jun 19, 2021
8221658
One sentence a line in instructions
mpizenberg Jun 19, 2021
497fe6c
Use curly crace syntax instead of constructors
ceddlyburge Jun 21, 2021
7aa6128
Use idiomatic elm parameter order
ceddlyburge Jun 21, 2021
6f1571d
Remove isSameTeam from exercise
ceddlyburge Jun 21, 2021
89640a2
Require type alias fields to be in correct order
ceddlyburge Jun 22, 2021
e16256e
Add requirement for a 'constructor' function
ceddlyburge Jun 22, 2021
3dfb723
Test that extensible records are supported
ceddlyburge Jun 22, 2021
8b6254c
Run elm-format
ceddlyburge Jun 22, 2021
1b2db1e
Add type annotations to list concept exercise
ceddlyburge Jun 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions concepts/lists/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ As lists are immutable, once a list has been constructed, its value can never ch
Elm lists have a _head_ (the first element) and a _tail_ (everything after the first element).
The tail of a list is itself a list.

Type annotations for lists can be defined as follows

```elm
List String --> a list of String
List Int --> a list of Int
```

Lists can be defined as follows:

```elm
empty : List Char
empty = []

singleValue = [ 5 ]
singleValueAlternative = List.singleton 5
singleValue = [ 5 ] --> List Int
singleValueAlternative = List.singleton 5 --> List Int

threeValues = [ "a", "b", "c" ]
threeValues = [ "a", "b", "c" ] --> List String
```

The most common way to add an element to a list is through the `::` (cons) operator:
Expand Down
9 changes: 9 additions & 0 deletions concepts/records/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"blurb": "Learn how to use records in an Elm program",
"authors": [
"ceddlyburge"
],
"contributors": [
"mpizenberg"
]
}
160 changes: 160 additions & 0 deletions concepts/records/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# About

[Records][records] are data structures grouping together related information with labels.
They are similar to objects in JavaScript or Java, or structs in C or Rust, but with some key distinctions.

## Creating records

Records are [created][create] with curly brackets and their elements are separated by commas.

```elm
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
```

The type of a record is also defined with a similar syntax, except equal signs `=` are replaced by colon signs `:`.

```elm
firefly : { name : String, creator : String, episodes : Int }
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
```

Repeating these type definitions is cumbersome, so using a [type alias][record-types] to share the definition is a common idiom.

```elm
type alias TvSeries =
{ name : String
, creator : String
, episodes : Int
}

firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
```

The type alias also automatically supplies a constructor function for the record.
This is sometimes surprising since functions in Elm must start with a lowercase character, but type constructors are one exception.

```elm
firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14
```

## Accessing record fields

The main way to [access a field value of a record instance][access] is to use the dot-notation such as `firefly.creator`.
The Elm compiler also supports special accessor functions starting with a dot `.` such as `.creator` that will work on any record with a field of that name.
That is the second exception to the rule that functions must start with a lowercase character.
In fact user-defined functions must start with a lowercase character, but the two examples we just discovered are generated by the compiler, not the programmer.

```elm
firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}

firefly.name
--> "Firefly"

.creator firefly
--> "Joss Whedon"
```

Records can also be _destructured_ in bindings using the [record pattern matching][pattern-matching].
Destructuring works with any record that has fields of the relevant names.

```elm
episodesCount : TvSeries -> Int
episodesCount { episodes } =
episodes

complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
let
{ name, creator } = show
in
{ name = name
, creator = creator
, episodes = episodesCount show
}
```

## Updating records

Records are immutable, as everything in Elm, so once a record is defined, its field values can never change.
There is a [special syntax to create a copy of an existing record, but changing one or more fields][update].
This syntax uses the pipe symbol `|` to distinguish the original record and the fields that will change, such as `{ oldRecord | field1 = newValue }`.

```elm
firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}

updatedFirefly : TvSeries
updatedFirefly =
{ firefly | creator = "J Whedon", episodes = 15 }
```

## Comparing records

Elm uses [structural equality][equality], which means that two instances of the same record with identical values are equal.

```elm
firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14

firefly1 == firefly2
--> True
```

## Extensible records

Elm also supports [structural typing][structural-typing] meaning that if a function requires a record with an x and y field, it will work with any record that has those fields such as 2D points, 3D points, spaceships, etc.
Those record types have a pipe `|` in their definition, such as `{ a | x : Float, y : Float }` and are called "extensible records" in Elm terminology.
Beware of the difference between a pipe `|` in a type definition, which is an extensible record definition (`{ a | x : Int }`), and a pipe `|` an actual record instance which means that we are updating some fields of that record.

```elm
point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }

.x point2d --> 1
.x point3d --> 3

length : { a | x : Float, y : Float } -> Float
length vector =
sqrt (vector.x * vector.x + vector.y * vector.y)

length point2d --> 2.236068
length point3d --> 5

translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
{ vector | x = vector.x + 1 }

translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }
```

[records]: https://elm-lang.org/docs/records
[record-types]: https://elm-lang.org/docs/records#record-types
[structural-typing]: https://en.wikipedia.org/wiki/Structural_type_system
[create]: https://elm-lang.org/docs/records#what-is-a-record-
[update]: https://elm-lang.org/docs/records#updating-records
[access]: https://elm-lang.org/docs/records#access
[pattern-matching]: https://elm-lang.org/docs/records#pattern-matching
160 changes: 160 additions & 0 deletions concepts/records/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# About

[Records][records] are data structures grouping together related information with labels.
They are similar to objects in JavaScript or Java, or structs in C or Rust, but with some key distinctions.

## Creating records

Records are [created][create] with curly brackets and their elements are separated by commas.

```elm
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
```

The type of a record is also defined with a similar syntax, except equal signs `=` are replaced by colon signs `:`.

```elm
firefly : { name : String, creator : String, episodes : Int }
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
```

Repeating these type definitions is cumbersome, so using a [type alias][record-types] to share the definition is a common idiom.

```elm
type alias TvSeries =
{ name : String
, creator : String
, episodes : Int
}

firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
```

The type alias also automatically supplies a constructor function for the record.
This is sometimes surprising since functions in Elm must start with a lowercase character, but type constructors are one exception.

```elm
firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14
```

## Accessing record fields

The main way to [access a field value of a record instance][access] is to use the dot-notation such as `firefly.creator`.
The Elm compiler also supports special accessor functions starting with a dot `.` such as `.creator` that will work on any record with a field of that name.
That is the second exception to the rule that functions must start with a lowercase character.
In fact user-defined functions must start with a lowercase character, but the two examples we just discovered are generated by the compiler, not the programmer.

```elm
firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}

firefly.name
--> "Firefly"

.creator firefly
--> "Joss Whedon"
```

Records can also be _destructured_ in bindings using the [record pattern matching][pattern-matching].
Destructuring works with any record that has fields of the relevant names.

```elm
episodesCount : TvSeries -> Int
episodesCount { episodes } =
episodes

complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
let
{ name, creator } = show
in
{ name = name
, creator = creator
, episodes = episodesCount show
}
```

## Updating records

Records are immutable, as everything in Elm, so once a record is defined, its field values can never change.
There is a [special syntax to create a copy of an existing record, but changing one or more fields][update].
This syntax uses the pipe symbol `|` to distinguish the original record and the fields that will change, such as `{ oldRecord | field1 = newValue }`.

```elm
firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}

updatedFirefly : TvSeries
updatedFirefly =
{ firefly | creator = "J Whedon", episodes = 15 }
```

## Comparing records

Elm uses [structural equality][equality], which means that two instances of the same record with identical values are equal.

```elm
firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14

firefly1 == firefly2
--> True
```

## Extensible records

Elm also supports [structural typing][structural-typing] meaning that if a function requires a record with an x and y field, it will work with any record that has those fields such as 2D points, 3D points, spaceships, etc.
Those record types have a pipe `|` in their definition, such as `{ a | x : Float, y : Float }` and are called "extensible records" in Elm terminology.
Beware of the difference between a pipe `|` in a type definition, which is an extensible record definition (`{ a | x : Int }`), and a pipe `|` an actual record instance which means that we are updating some fields of that record.

```elm
point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }

a.x point2d --> 1
.x point3d --> 3

length : { a | x : Float, y : Float } -> Float
length vector =
sqrt (vector.x * vector.x + vector.y * vector.y)

length point2d --> 2.236068
length point3d --> 5

translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
{ vector | x = vector.x + 1 }

translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }
```

[records]: https://elm-lang.org/docs/records
[record-types]: https://elm-lang.org/docs/records#record-types
[structural-typing]: https://en.wikipedia.org/wiki/Structural_type_system
[create]: https://elm-lang.org/docs/records#what-is-a-record-
[update]: https://elm-lang.org/docs/records#updating-records
[access]: https://elm-lang.org/docs/records#access
[pattern-matching]: https://elm-lang.org/docs/records#pattern-matching
18 changes: 18 additions & 0 deletions concepts/records/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"url": "https://elm-lang.org/docs/records",
"description": "Records"
},
{
"url": "https://elmprogramming.com/record.html",
"description": "Records (in more detail)"
},
{
"url": "https://en.wikipedia.org/wiki/Structural_type_system",
"description": "Structural types"
},
{
"url": "https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/scopedlabels.pdf",
"description": "Extensible records white paper"
}
]
Loading