Skip to content

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

Merged
merged 2 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions concepts/algebraic-data-types/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"authors": [
"pwadsworth"
],
"blurb": "Introduction to Algebraic Data Types in Haskell."
}
40 changes: 40 additions & 0 deletions concepts/algebraic-data-types/about.md
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"
```
41 changes: 41 additions & 0 deletions concepts/algebraic-data-types/introduction.md
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"
```
3 changes: 3 additions & 0 deletions concepts/algebraic-data-types/introduction.md.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction

%{concept:algebraic-data-types}
10 changes: 10 additions & 0 deletions concepts/algebraic-data-types/links.json
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"
}
]
18 changes: 18 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@
"concepts": [
"pattern-matching-literals"
]
},
{
"slug": "valentines-day",
"name": "Valentines Day",
"uuid": "537d8df3-0b12-4dbe-aa86-65ae79c4de0e",
"prerequisites": [
"pattern-matching-literals",
"numbers"
],
"status": "beta",
"concepts": [
"algebraic-data-types"
]
}
],
"practice": [
Expand Down Expand Up @@ -1267,6 +1280,11 @@
"uuid": "ca21a553-6fc1-49be-8f47-730f97330862",
"slug": "pattern-matching-literals",
"name": "Pattern Matching Literals"
},
{
"uuid": "cb756cfd-9b5c-408d-bf3f-b2adc8322da3",
"slug": "algebraic-data-types",
"name": "Algebraic Data Types"
}
],
"key_features": [
Expand Down
29 changes: 29 additions & 0 deletions exercises/concept/valentines-day/.docs/hints.md
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
63 changes: 63 additions & 0 deletions exercises/concept/valentines-day/.docs/instructions.md
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.
Copy link
Member

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.


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
```
41 changes: 41 additions & 0 deletions exercises/concept/valentines-day/.docs/introduction.md
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"
```
2 changes: 2 additions & 0 deletions exercises/concept/valentines-day/.meta/DONT-TEST-STUB
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.
24 changes: 24 additions & 0 deletions exercises/concept/valentines-day/.meta/config.json
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"
]
}
}
28 changes: 28 additions & 0 deletions exercises/concept/valentines-day/.meta/design.md
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
18 changes: 18 additions & 0 deletions exercises/concept/valentines-day/.meta/exemplar/package.yaml
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation uses case, which I don't think is being taught in the prerequisites, correct? If so, we'll need to add another exercise to teach that or rewrite this to use the pattern matching functionality that was already introduced.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case is introduced in this concept (last paragraph of introduction.md)

Restaurant Korean -> Yes
Restaurant Turkish -> Maybe
Movie Romance -> Yes
Movie _ -> No
Walk kilometers
| kilometers < 3 -> Yes
| kilometers <= 5 -> Maybe
_ -> No
Loading