diff --git a/concepts/algebraic-data-types/.meta/config.json b/concepts/algebraic-data-types/.meta/config.json new file mode 100644 index 000000000..8ebd38616 --- /dev/null +++ b/concepts/algebraic-data-types/.meta/config.json @@ -0,0 +1,6 @@ +{ + "authors": [ + "pwadsworth" + ], + "blurb": "Introduction to Algebraic Data Types in Haskell." +} diff --git a/concepts/algebraic-data-types/about.md b/concepts/algebraic-data-types/about.md new file mode 100644 index 000000000..5633d5aca --- /dev/null +++ b/concepts/algebraic-data-types/about.md @@ -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" +``` diff --git a/concepts/algebraic-data-types/introduction.md b/concepts/algebraic-data-types/introduction.md new file mode 100644 index 000000000..f25d8de26 --- /dev/null +++ b/concepts/algebraic-data-types/introduction.md @@ -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" +``` diff --git a/concepts/algebraic-data-types/introduction.md.tpl b/concepts/algebraic-data-types/introduction.md.tpl new file mode 100644 index 000000000..fde1150c2 --- /dev/null +++ b/concepts/algebraic-data-types/introduction.md.tpl @@ -0,0 +1,3 @@ +# Introduction + +%{concept:algebraic-data-types} diff --git a/concepts/algebraic-data-types/links.json b/concepts/algebraic-data-types/links.json new file mode 100644 index 000000000..37dc684da --- /dev/null +++ b/concepts/algebraic-data-types/links.json @@ -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" + } +] diff --git a/config.json b/config.json index 9fd117593..3ebd76f6f 100644 --- a/config.json +++ b/config.json @@ -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": [ @@ -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": [ diff --git a/exercises/concept/valentines-day/.docs/hints.md b/exercises/concept/valentines-day/.docs/hints.md new file mode 100644 index 000000000..97bce2b0e --- /dev/null +++ b/exercises/concept/valentines-day/.docs/hints.md @@ -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 diff --git a/exercises/concept/valentines-day/.docs/instructions.md b/exercises/concept/valentines-day/.docs/instructions.md new file mode 100644 index 000000000..c79daabdb --- /dev/null +++ b/exercises/concept/valentines-day/.docs/instructions.md @@ -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 +``` diff --git a/exercises/concept/valentines-day/.docs/introduction.md b/exercises/concept/valentines-day/.docs/introduction.md new file mode 100644 index 000000000..f25d8de26 --- /dev/null +++ b/exercises/concept/valentines-day/.docs/introduction.md @@ -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" +``` diff --git a/exercises/concept/valentines-day/.meta/DONT-TEST-STUB b/exercises/concept/valentines-day/.meta/DONT-TEST-STUB new file mode 100644 index 000000000..fe60d01c1 --- /dev/null +++ b/exercises/concept/valentines-day/.meta/DONT-TEST-STUB @@ -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. diff --git a/exercises/concept/valentines-day/.meta/config.json b/exercises/concept/valentines-day/.meta/config.json new file mode 100644 index 000000000..a17c285b5 --- /dev/null +++ b/exercises/concept/valentines-day/.meta/config.json @@ -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" + ] + } +} diff --git a/exercises/concept/valentines-day/.meta/design.md b/exercises/concept/valentines-day/.meta/design.md new file mode 100644 index 000000000..e2506c2e3 --- /dev/null +++ b/exercises/concept/valentines-day/.meta/design.md @@ -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 diff --git a/exercises/concept/valentines-day/.meta/exemplar/package.yaml b/exercises/concept/valentines-day/.meta/exemplar/package.yaml new file mode 100644 index 000000000..349d749fe --- /dev/null +++ b/exercises/concept/valentines-day/.meta/exemplar/package.yaml @@ -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 diff --git a/exercises/concept/valentines-day/.meta/exemplar/src/ValentinesDay.hs b/exercises/concept/valentines-day/.meta/exemplar/src/ValentinesDay.hs new file mode 100644 index 000000000..a0d1c6581 --- /dev/null +++ b/exercises/concept/valentines-day/.meta/exemplar/src/ValentinesDay.hs @@ -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 + Restaurant Korean -> Yes + Restaurant Turkish -> Maybe + Movie Romance -> Yes + Movie _ -> No + Walk kilometers + | kilometers < 3 -> Yes + | kilometers <= 5 -> Maybe + _ -> No diff --git a/exercises/concept/valentines-day/package.yaml b/exercises/concept/valentines-day/package.yaml new file mode 100644 index 000000000..ae8b930f1 --- /dev/null +++ b/exercises/concept/valentines-day/package.yaml @@ -0,0 +1,19 @@ +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 diff --git a/exercises/concept/valentines-day/src/ValentinesDay.hs b/exercises/concept/valentines-day/src/ValentinesDay.hs new file mode 100644 index 000000000..b0cc89054 --- /dev/null +++ b/exercises/concept/valentines-day/src/ValentinesDay.hs @@ -0,0 +1,14 @@ +module ValentinesDay where + +-- Define the function and required algebraic data types (ADT) below. + +data Approval = ImplementApproval + +data Cuisine = ImplementCuisine + +data Genre = ImplementGenre + +data Activity = ImplementActivity + +rateActivity :: Activity -> Approval +rateActivity activity = error "Implement rateActivity" diff --git a/exercises/concept/valentines-day/stack.yaml b/exercises/concept/valentines-day/stack.yaml new file mode 100644 index 000000000..e084154be --- /dev/null +++ b/exercises/concept/valentines-day/stack.yaml @@ -0,0 +1 @@ +resolver: lts-19.27 diff --git a/exercises/concept/valentines-day/test/Tests.hs b/exercises/concept/valentines-day/test/Tests.hs new file mode 100644 index 000000000..ddb908ff0 --- /dev/null +++ b/exercises/concept/valentines-day/test/Tests.hs @@ -0,0 +1,54 @@ +import Test.Hspec (describe, hspec, it) +import ValentinesDay (Activity (..), Approval (..), Cuisine (..), Genre (..), rateActivity) + +main :: IO () +main = hspec $ + describe "ValentinesDay" $ do + it "chill rated no" $ + case rateActivity Chill of + No -> True + _ -> False + it "board game rated no" $ + case rateActivity BoardGame of + No -> True + _ -> False + it "crime movie rated no" $ + case rateActivity (Movie Crime) of + No -> True + _ -> False + it "horror movie rated no" $ + case rateActivity (Movie Horror) of + No -> True + _ -> False + it "romance movie rated yes" $ + case rateActivity (Movie Romance) of + Yes -> True + _ -> False + it "thriller movie rated no" $ + case rateActivity (Movie Thriller) of + No -> True + _ -> False + it "korean restaurant rated yes" $ + case rateActivity (Restaurant Korean) of + Yes -> True + _ -> False + it "turkish restaurant rated maybe" $ + case rateActivity (Restaurant Turkish) of + Maybe -> True + _ -> False + it "walk 2 kilometers rated yes" $ + case rateActivity (Walk 2) of + Yes -> True + _ -> False + it "walk 3 kilometers rated maybe" $ + case rateActivity (Walk 3) of + Maybe -> True + _ -> False + it "walk 5 kilometers rated maybe" $ + case rateActivity (Walk 5) of + Maybe -> True + _ -> False + it "walk 6 kilometers rated no" $ + case rateActivity (Walk 6) of + No -> True + _ -> False diff --git a/reference/concepts.md b/reference/concepts.md index a3cdcf966..52542a03d 100644 --- a/reference/concepts.md +++ b/reference/concepts.md @@ -87,7 +87,7 @@ The Haskell concept exercises are based on concepts. The list below contains the - [ ] Queues - [ ] Sets - [ ] Stacks -- [ ] Algebraic data types +- [x] Algebraic data types - [ ] Numbers (`Num` & Co.) - [x] Floating point numbers - [x] Signed integers (`Int`, `Integer`)