-
-
Notifications
You must be signed in to change notification settings - Fork 195
Concept Algebraic Data Types with exercise #1110
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"authors": [ | ||
"pwadsworth" | ||
], | ||
"blurb": "Introduction to Algebraic Data Types in Haskell." | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Introduction | ||
|
||
An Algebraic Data Type (ADT) represents a fixed number of named cases. | ||
Each value of an ADT corresponds to exactly one of the named cases. | ||
|
||
An ADT is defined using the `data` keyword, with cases separated by pipe (`|`) characters. | ||
If none of the cases have data associated with them the ADT is similar to what other languages usually refer to as an _enumeration_ (or _enum_). | ||
|
||
```haskell | ||
data Season | ||
= Spring | ||
| Summer | ||
| Autumn | ||
| Winter | ||
``` | ||
|
||
Each case of an ADT can optionally have data associated with it, and different cases can have different types of data. When the case has data associated, a constructor is required. | ||
|
||
```haskell | ||
data Number | ||
= NInt Int --'NInt' is the constructor for an Int Number. | ||
| NFloat Float --'NFloat' is the constructor for an Float Number. | ||
| Invalid --'Invalid' does not have data associated to it. | ||
``` | ||
|
||
Creating a value for a specific case can be done by referring to its name (e.g, `NInt 22`). | ||
As case names are just constructor functions, associated data can be passed as a regular function argument. | ||
|
||
ADTs have _structural equality_, which means that two values for the same case and with the same (optional) data are equivalent. | ||
|
||
While one can use `if/else` expressions to work with ADTs, the recommended way to work with them is through pattern matching using _case_ statement: | ||
|
||
```haskell | ||
add1 :: Number -> String | ||
add1 number = | ||
case number of | ||
NInt i -> show (i + 1) | ||
NFloat f -> show (f + 1.0) | ||
Invalid -> error "Invalid input" | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Introduction | ||
|
||
An Algebraic Data Type (ADT) represents a fixed number of named cases. | ||
Each value of an ADT corresponds to exactly one of the named cases. | ||
|
||
An ADT is defined using the `data` keyword, with cases separated by pipe (`|`) characters. | ||
If none of the cases have data associated with them the ADT is similar to what other languages usually refer to as an _enumeration_ (or _enum_). | ||
|
||
```haskell | ||
data Season | ||
= Spring | ||
| Summer | ||
| Autumn | ||
| Winter | ||
``` | ||
|
||
Each case of an ADT can optionally have data associated with it, and different cases can have different types of data. | ||
When the case has data associated, a constructor is required. | ||
|
||
```haskell | ||
data Number | ||
= NInt Int --'NInt' is the constructor for an Int Number. | ||
| NFloat Float --'NFloat' is the constructor for an Float Number. | ||
| Invalid --'Invalid' does not have data associated to it. | ||
``` | ||
|
||
Creating a value for a specific case can be done by referring to its name (e.g, `NInt 22`). | ||
As case names are just constructor functions, associated data can be passed as a regular function argument. | ||
|
||
ADTs have _structural equality_, which means that two values for the same case and with the same (optional) data are equivalent. | ||
|
||
While one can use `if/else` expressions to work with ADTs, the recommended way to work with them is through pattern matching using _case_ statement: | ||
|
||
```haskell | ||
add1 :: Number -> String | ||
add1 number = | ||
case number of | ||
NInt i -> show (i + 1) | ||
NFloat f -> show (f + 1.0) | ||
Invalid -> error "Invalid input" | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Introduction | ||
|
||
%{concept:algebraic-data-types} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[ | ||
{ | ||
"url": "https://learnyouahaskell.github.io/making-our-own-types-and-typeclasses.html#algebraic-data-types", | ||
"description": "Algebraic Data Types" | ||
}, | ||
{ | ||
"url": "https://learnyouahaskell.github.io/making-our-own-types-and-typeclasses.html#type-parameters", | ||
"description": "Type variables" | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Hints | ||
|
||
## 1. Define the approval | ||
|
||
- [Define the algebraic data type][ADT] `Approval` with constructors for the required options. | ||
|
||
## 2. Define the cuisine | ||
|
||
- [Define the algebraic data type][ADT] `Cuisine` with constructors for the required options. | ||
|
||
## 3. Define the movie genres | ||
|
||
- [Define the algebraic data type][ADT] `Genre` with constructors for the required options. | ||
|
||
## 4. Define the activity | ||
|
||
- [Define an algebraic data type with associated data][ADT-with-data] to encapsulate the different activities. | ||
|
||
## 5. Rate the activity | ||
|
||
- The best way to execute logic based on the activity's value is to use [case expressions][case-expression]. | ||
- Pattern matching an algebraic data type case provides access to its associated data. | ||
- To add an additional condition to a pattern, you can use a [guard][guards] inside a case. | ||
- If you want to catch all other possible values in one case, you can use the wildcard pattern `_`. | ||
|
||
[ADT]: https://www.schoolofhaskell.com/school/starting-with-haskell/introduction-to-haskell/2-algebraic-data-types#enumeration-types | ||
[ADT-with-data]: https://www.schoolofhaskell.com/school/starting-with-haskell/introduction-to-haskell/2-algebraic-data-types#beyond-enumerations | ||
[case-expression]: https://www.schoolofhaskell.com/school/starting-with-haskell/introduction-to-haskell/2-algebraic-data-types#case-expessions | ||
[guards]: https://learnyouahaskell.github.io/syntax-in-functions.html#guards-guards |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Instructions | ||
|
||
In this exercise, it's Valentine's day and you are planning what to do with your partner. | ||
Your partner has lots of ideas, and is asking you to rate the ideas, in order to find the best activity. | ||
|
||
The following ideas are proposed by your partner: | ||
|
||
- Playing a board game | ||
- Chill out | ||
- Watch a movie | ||
- Go to a restaurant | ||
- Take a walk | ||
|
||
You have six tasks to help choose your Valentine's day activity. | ||
|
||
## 1. Define the approval | ||
|
||
For each idea your partner proposes, you respond with one of three options: yes, no or maybe. | ||
|
||
Define the `Approval` algebraic data type to represent these options for the following three cases: `Yes`, `No` or `Maybe`. | ||
|
||
## 2. Define the cuisines | ||
|
||
Your partner has selected two possible restaurants: one based on Korean cuisine and the other based on Turkish cuisine. | ||
|
||
Define the `Cuisine` algebraic data type to represent these restaurants as the following two cases: `Korean` or `Turkish`. | ||
|
||
## 3. Define the movie genres | ||
|
||
There are tons of movies to choose from, so to narrow things down, your partner also lists their preferred genre. | ||
|
||
Define the `Genre` algebraic data type to represent the following genres cases: `Crime`, `Horror`, `Romance` or `Thriller`. | ||
|
||
## 4. Define the activity | ||
|
||
As mentioned, your partner has come up with five possible activities: playing a board game, chill out, watch a movie, go to a restaurant and taking a walk. | ||
|
||
Define the `Activity` algebraic data type to represent these activity types: | ||
|
||
- `BoardGame`: no associated data. | ||
- `Chill`: no associated data. | ||
- `Movie`: has its `Genre` as associated data. | ||
- `Restaurant`: has its `Cuisine` as associated data. | ||
- `Walk`: has an `Int` representing the number of kilometers to walk as associated data. | ||
|
||
## 5. Rate the activity | ||
|
||
Finally, you're ready to rate your partner's ideas. | ||
This is how you feel about your partner's idea: | ||
|
||
- Playing a board game: no. | ||
- Chill out: no. | ||
- Watch a movie: yes if it is a romantic movie; otherwise, no. | ||
- Go to a restaurant: yes if the cuisine is Korean, maybe if it is Turkish. | ||
- Take a walk: yes if the walk is less than three kilometers; maybe if it is between three and five kilometers (inclusive); otherwise, no. | ||
|
||
Implement a function named `rateActivity` that takes an `Activity` value and returns the `Approval` based on the above sentiments. | ||
For example: | ||
|
||
```haskell | ||
rateActivity (Restaurant Turkish) | ||
-- -> Maybe | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Introduction | ||
|
||
An Algebraic Data Type (ADT) represents a fixed number of named cases. | ||
Each value of an ADT corresponds to exactly one of the named cases. | ||
|
||
An ADT is defined using the `data` keyword, with cases separated by pipe (`|`) characters. | ||
If none of the cases have data associated with them the ADT is similar to what other languages usually refer to as an _enumeration_ (or _enum_). | ||
|
||
```haskell | ||
data Season | ||
= Spring | ||
| Summer | ||
| Autumn | ||
| Winter | ||
``` | ||
|
||
Each case of an ADT can optionally have data associated with it, and different cases can have different types of data. | ||
When the case has data associated, a constructor is required. | ||
|
||
```haskell | ||
data Number | ||
= NInt Int --'NInt' is the constructor for an Int Number. | ||
| NFloat Float --'NFloat' is the constructor for an Float Number. | ||
| Invalid --'Invalid' does not have data associated to it. | ||
``` | ||
|
||
Creating a value for a specific case can be done by referring to its name (e.g, `NInt 22`). | ||
As case names are just constructor functions, associated data can be passed as a regular function argument. | ||
|
||
ADTs have _structural equality_, which means that two values for the same case and with the same (optional) data are equivalent. | ||
|
||
While one can use `if/else` expressions to work with ADTs, the recommended way to work with them is through pattern matching using _case_ statement: | ||
|
||
```haskell | ||
add1 :: Number -> String | ||
add1 number = | ||
case number of | ||
NInt i -> show (i + 1) | ||
NFloat f -> show (f + 1.0) | ||
Invalid -> error "Invalid input" | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Teaches the basics of defining data types, and we found that people usually learn best when they have to write things from scratch. | ||
Thus, the stub lacks the data type definitions for the tests. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"blurb": "Learn about algebraic data types by deciding what activity to surprise your partner with on Valentines Day.", | ||
"authors": [ | ||
"pwadsworth" | ||
], | ||
"forked_from": [ | ||
"fsharp/valentines-day" | ||
], | ||
"files": { | ||
"solution": [ | ||
"src/ValentinesDay.hs", | ||
"package.yaml" | ||
], | ||
"test": [ | ||
"test/Tests.hs" | ||
], | ||
"exemplar": [ | ||
".meta/exemplar/src/ValentinesDay.hs" | ||
], | ||
"invalidator": [ | ||
"stack.yaml" | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Design | ||
|
||
## Learning objectives | ||
|
||
- Know what [Algebraic Data Types][ADT] (ADT) are. | ||
- Know how ADTs are different from enums. | ||
- Know how to define ADT, with and without data. | ||
- Know how to pattern match on ADTs using [case expressions][case-expression]. | ||
|
||
## Out of scope | ||
|
||
- Recursive ADT. | ||
- Single type wrapper ADT. | ||
- Active patterns. | ||
- Adding members to ADT. | ||
- `function` pattern shorthand notation. | ||
|
||
## Concepts | ||
|
||
- `Algebraic Data Types`: know what ADTs are; know how ADTs are different from enums; know how to define an ADT, with and without data; know how to pattern match on ADTs. | ||
|
||
## Prerequisites | ||
|
||
- `basics`: defining functions and scoping and using integers. | ||
- `pattern-matching`: know how to do pattern matching. | ||
|
||
[ADT]: https://www.schoolofhaskell.com/school/starting-with-haskell/introduction-to-haskell/2-algebraic-data-types#enumeration-types | ||
[case-expression]: https://www.schoolofhaskell.com/school/starting-with-haskell/introduction-to-haskell/2-algebraic-data-types#case-expessions |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: valentines-day | ||
version: 1.0.0.0 | ||
|
||
dependencies: | ||
- base | ||
|
||
library: | ||
exposed-modules: ValentinesDay | ||
source-dirs: src | ||
ghc-options: -Wall | ||
|
||
tests: | ||
test: | ||
main: Tests.hs | ||
source-dirs: test | ||
dependencies: | ||
- valentines-day | ||
- hspec |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
module ValentinesDay (rateActivity, Approval (..), Cuisine (..), Genre (..), Activity (..)) where | ||
|
||
data Approval | ||
= Yes | ||
| No | ||
| Maybe | ||
|
||
data Cuisine | ||
= Korean | ||
| Turkish | ||
|
||
data Genre | ||
= Crime | ||
| Horror | ||
| Romance | ||
| Thriller | ||
|
||
data Activity | ||
= BoardGame | ||
| Chill | ||
| Movie Genre | ||
| Restaurant Cuisine | ||
| Walk Int | ||
|
||
rateActivity :: Activity -> Approval | ||
rateActivity activity = | ||
case activity of | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
Restaurant Korean -> Yes | ||
Restaurant Turkish -> Maybe | ||
Movie Romance -> Yes | ||
Movie _ -> No | ||
Walk kilometers | ||
| kilometers < 3 -> Yes | ||
| kilometers <= 5 -> Maybe | ||
_ -> No |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the
(inclusive)
here. Even though I think it was fairly obvious it's inclusive (if it wasn't, the only value that gets a Maybe would have been 4, the answer for 3 would have been undefined), I wanted to be explicit anyway.