From 744d7b1eeeb24258088ffec47727f40593a9f9f7 Mon Sep 17 00:00:00 2001 From: meatball Date: Tue, 1 Oct 2024 10:34:11 +0200 Subject: [PATCH 1/5] Start --- concepts/pattern-matching/.meta/config.json | 6 +++ concepts/pattern-matching/about.md | 41 +++++++++++++++ concepts/pattern-matching/introduction.md | 55 +++++++++++++++++++++ concepts/pattern-matching/links.json | 10 ++++ 4 files changed, 112 insertions(+) create mode 100644 concepts/pattern-matching/.meta/config.json create mode 100644 concepts/pattern-matching/about.md create mode 100644 concepts/pattern-matching/introduction.md create mode 100644 concepts/pattern-matching/links.json diff --git a/concepts/pattern-matching/.meta/config.json b/concepts/pattern-matching/.meta/config.json new file mode 100644 index 000000000..d76c33420 --- /dev/null +++ b/concepts/pattern-matching/.meta/config.json @@ -0,0 +1,6 @@ +{ + "authors": [ + "pwadsworth" + ], + "blurb": "Introduction to pattern matching in Haskell." +} diff --git a/concepts/pattern-matching/about.md b/concepts/pattern-matching/about.md new file mode 100644 index 000000000..802d2cb6f --- /dev/null +++ b/concepts/pattern-matching/about.md @@ -0,0 +1,41 @@ +# About + +When writing Haskell functions, we can make use of an [assertive style][assertive-style] with [pattern matching][pattern-match-doc]: + +```haskell +read_file = do + {:ok, contents} <- readFile "hello.txt" + contents +``` + +- Pattern matching is explicitly performed using the match operator, [`=/2`][match-op]. + + - Matches succeed when the _shape_ of the data on the left side of the operator matches the right side. + - When matches succeed, variables on the left are bound to the values on the right. + - Using an underscore, `_` (also know as wildcard), allows us to disregard the values in those places. + + ```haskell + (True, number, _) <- (True, 5, [4.5, 6.3]) + number + -- -> 5 is bound to this variable + ``` + +- Pattern matches may also occur in a function clause head, so that only arguments that match the pattern will invoke the function. +- Variables can be bound in a function clause pattern match. + + ```haskell + named_function :: Bool -> (Bool, Int) + named_function True = (True, 1) + + named_function(True) + -- -> (True, 1) + -- The first function clause matches, so it is invoked + + named_function(False) + -- (FunctionClauseError) Non-exhaustive patterns in function named_function + ``` + +[assertive-style]: https://blog.plataformatec.com.br/2014/09/writing-assertive-code-with-elixir/ +[pattern-match-doc]: https://hexdocs.pm/elixir/pattern-matching.html +[match-op]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#=/2 +[getting-started-pin-operator]: https://hexdocs.pm/elixir/pattern-matching.html#the-pin-operator diff --git a/concepts/pattern-matching/introduction.md b/concepts/pattern-matching/introduction.md new file mode 100644 index 000000000..7ce106582 --- /dev/null +++ b/concepts/pattern-matching/introduction.md @@ -0,0 +1,55 @@ +# Introduction + +While `if/else` expressions can be used to execute conditional logic, Haskell also has a more powerful way to execute conditional logic: [pattern matching][pattern-matching]. +With pattern matching, a value can be tested against one or more _patterns_. +An example of such a pattern is the _constant pattern_, which matches a value against a constant (e.g. `1` or `"hello"`). + +When defining functions, you can define separate function bodies for different patterns. +This leads to clean code that is simple and readable. +You can pattern match on any data type — numbers, characters, lists, tuples, etc. + +For example, a trivial function that takes a whole number (`Int`) and makes it _1_ closer to _0_ could be expressed like this: + +```haskell +closerToZero :: Int -> Int +closerToZero 0 = 0 +closerToZero 1 = 0 +``` + +Pattern matching starts to shine when used together with other patterns, for example the _variable pattern_: + +```haskell +closerToZero :: Int -> Int +closerToZero 0 = 0 +closerToZero n = n - 1 +``` + +The above example treats all inputs other than _0_ the same, and would produce incorrect results for negative numbers. +This can be solved using conditional patterns, known as _guards_, which are expressed with the `|` symbol: + +```haskell +closerToZero :: Int -> Int +closerToZero n + | n < 0 = n + 1 + | n > 0 = n - 1 +``` + +In the above examples not all possible inputs have a matching pattern. +The compiler will detect this and output a warning. +This is a very useful feature of Haskell that helps ensure all possible paths are covered to avoid run-time errors. +It is known as _exhaustive checking_. +To solve the warning, you have to handle all cases. +Within _guards_ you can use the expression `otherwise` as syntactic sugar for `True` to catch all remaining patterns. + +```haskell +closerToZero :: Int -> Int +closerToZero n + | n < 0 = n + 1 + | n > 0 = n - 1 + | otherwise = 0 +``` + +Pattern matching will test a value against each pattern from top to bottom, until it finds a matching pattern and executes the logic associated with that pattern. +**The order of patterns matters!** + +[pattern-matching]: https://learnyouahaskell.github.io/syntax-in-functions#pattern-matching diff --git a/concepts/pattern-matching/links.json b/concepts/pattern-matching/links.json new file mode 100644 index 000000000..f0c7e057a --- /dev/null +++ b/concepts/pattern-matching/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://learnyouahaskell.github.io/syntax-in-functions", + "description": "Pattern matching syntax in functions" + }, + { + "url": "https://learnyouahaskell.github.io/syntax-in-functions#guards-guards", + "description": "Use of guards for pattern matching" + } +] From 351b322d6928bc653873424475daacfec05582f4 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 5 Oct 2024 19:19:44 +0200 Subject: [PATCH 2/5] Add concept and exercise --- concepts/pattern-matching/.meta/config.json | 2 +- concepts/pattern-matching/about.md | 104 ++++++++++++----- concepts/pattern-matching/introduction.md | 104 +++++++++++------ concepts/pattern-matching/links.json | 8 +- concepts/tuple/.meta/config.json | 6 + concepts/tuple/about.md | 27 +++++ concepts/tuple/introduction.md | 27 +++++ concepts/tuple/links.json | 10 ++ config.json | 23 ++++ .../concept/KitchenCalculator/.docs/hints.md | 30 +++++ .../KitchenCalculator/.docs/instructions.md | 57 +++++++++ .../KitchenCalculator/.docs/introduction.md | 109 ++++++++++++++++++ .../KitchenCalculator/.meta/config.json | 25 ++++ .../concept/KitchenCalculator/.meta/design.md | 34 ++++++ .../.meta/exemplar/package.yaml | 18 +++ .../.meta/exemplar/src/KitchenCalculator.hs | 24 ++++ .../concept/KitchenCalculator/package.yaml | 18 +++ .../src/KitchenCalculator.hs | 15 +++ .../concept/KitchenCalculator/stack.yaml | 1 + .../concept/KitchenCalculator/test/Tests.hs | 61 ++++++++++ 20 files changed, 633 insertions(+), 70 deletions(-) create mode 100644 concepts/tuple/.meta/config.json create mode 100644 concepts/tuple/about.md create mode 100644 concepts/tuple/introduction.md create mode 100644 concepts/tuple/links.json create mode 100644 exercises/concept/KitchenCalculator/.docs/hints.md create mode 100644 exercises/concept/KitchenCalculator/.docs/instructions.md create mode 100644 exercises/concept/KitchenCalculator/.docs/introduction.md create mode 100644 exercises/concept/KitchenCalculator/.meta/config.json create mode 100644 exercises/concept/KitchenCalculator/.meta/design.md create mode 100644 exercises/concept/KitchenCalculator/.meta/exemplar/package.yaml create mode 100644 exercises/concept/KitchenCalculator/.meta/exemplar/src/KitchenCalculator.hs create mode 100644 exercises/concept/KitchenCalculator/package.yaml create mode 100644 exercises/concept/KitchenCalculator/src/KitchenCalculator.hs create mode 100644 exercises/concept/KitchenCalculator/stack.yaml create mode 100644 exercises/concept/KitchenCalculator/test/Tests.hs diff --git a/concepts/pattern-matching/.meta/config.json b/concepts/pattern-matching/.meta/config.json index d76c33420..70b7a5aac 100644 --- a/concepts/pattern-matching/.meta/config.json +++ b/concepts/pattern-matching/.meta/config.json @@ -1,6 +1,6 @@ { "authors": [ - "pwadsworth" + "meatball133" ], "blurb": "Introduction to pattern matching in Haskell." } diff --git a/concepts/pattern-matching/about.md b/concepts/pattern-matching/about.md index 802d2cb6f..32700e140 100644 --- a/concepts/pattern-matching/about.md +++ b/concepts/pattern-matching/about.md @@ -1,41 +1,89 @@ # About -When writing Haskell functions, we can make use of an [assertive style][assertive-style] with [pattern matching][pattern-match-doc]: +When writing Haskell functions, we can make use of [pattern matching][pattern-matching]. +Pattern matching is a very powerful feature of Haskell that allows you to match on data constructors and bind variables to the values inside. +As the name suggests, what we do is match values against patterns, and if the value matches the pattern, we can bind variables to the values inside the pattern. +Pattern matching is mainly built of these concepts: recognizing values, binding variables, and breaking down values. + +~~~~exercism/note +Pattern matching in languages such as Elixir is a bit different from pattern matching in Haskell. +Elixir for example allows multiple of same binding names in a pattern, while Haskell does not. +~~~~ + + +Take this function for example: + +```haskell +lucky :: Int -> String +lucky 7 = "Lucky number seven!" +lucky x = "Sorry, you're out of luck, pal!" +``` + +Here we have a function `lucky` that takes an `Int` and returns a `String`. +We have defined two patterns for the function, one that matches the number `7` and one that matches any other number, the name can be anything (as long as it follows Haskell naming convention), but we use `x` here. +If the number is `7`, the function will return `"Lucky number seven!"`, otherwise it will return `"Sorry, you're out of luck, pal!"`. +What is important to note here is that the patterns are checked from top to bottom, so if we had swapped the order of the patterns, the function would always return `"Lucky number seven!"`. + +## List patterns + +A very common pattern is to match on lists, and that is taking the head and the tail of the list. +This is due to lists nature of being a linked list. +Here is an example of a function that returns the head and the tail of a list: + +```haskell +headAndTail :: [Int] -> (Int, [Int]) +headAndTail [] = error "Can't call head on an empty list" +headAndTail (x:xs) = (x, xs) +``` + +We have two patterns here, one that matches an empty list and one that matches a list with at least one element. +This is due to if the list is empty, we need to have a case for that, otherwise we would get a runtime error. +If the list is not empty, we can match the head of the list with `x` and the tail of the list with `xs`. +This is done using the `:` (cons) operator, which is used to prepend an element to a list. +But in pattern matching it allows us to break down a list into its head and tail, so in a way doing the opposite. + +The `xs` is a common name for the tail of a list, it highlights that it is a list, but if you would be working with a nested list, you could use `xss` to highlight that it is a list of lists. + +## Tuple patterns + +As with lists, we can also match on tuples. +Here is an example of a function that takes a tuple and returns the first and second element: ```haskell -read_file = do - {:ok, contents} <- readFile "hello.txt" - contents +sumTuple :: (Int, Int) -> Int +sumTuple (x, y) = x + y ``` -- Pattern matching is explicitly performed using the match operator, [`=/2`][match-op]. +Here we have a pattern that matches a tuple with two elements, the first element is bound to `x` and the second element is bound to `y`. + +## Wildcard patterns + +Sometimes we don't care about the value of a variable, we just want to match on the pattern. +This is where the wildcard pattern comes in, it is denoted by an underscore `_`. - - Matches succeed when the _shape_ of the data on the left side of the operator matches the right side. - - When matches succeed, variables on the left are bound to the values on the right. - - Using an underscore, `_` (also know as wildcard), allows us to disregard the values in those places. +Here is an example of a function that returns the first element of a list: - ```haskell - (True, number, _) <- (True, 5, [4.5, 6.3]) - number - -- -> 5 is bound to this variable - ``` +```haskell +head' :: [Int] -> Int +head' [] = error "Can't call head on an empty list" +head' (x:_) = x +``` + +Here we say we don't need the tail of the list, so we use the wildcard pattern to ignore it. -- Pattern matches may also occur in a function clause head, so that only arguments that match the pattern will invoke the function. -- Variables can be bound in a function clause pattern match. +## Type patterns - ```haskell - named_function :: Bool -> (Bool, Int) - named_function True = (True, 1) +We can also match on types, this is done by using the constructor of said type. +We can also extract values from the type, like we did with tuples. - named_function(True) - -- -> (True, 1) - -- The first function clause matches, so it is invoked +```haskell +data AccountType = Guest | User String + +greet :: AccountType -> String +greet Guest = "Welcome, guest!" +greet (User name) = "Welcome, " ++ name ++ "!" +``` - named_function(False) - -- (FunctionClauseError) Non-exhaustive patterns in function named_function - ``` +In the first pattern we match on the `Guest` constructor, and in the second pattern we match on the `User` constructor and bind the value inside to `name`. -[assertive-style]: https://blog.plataformatec.com.br/2014/09/writing-assertive-code-with-elixir/ -[pattern-match-doc]: https://hexdocs.pm/elixir/pattern-matching.html -[match-op]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#=/2 -[getting-started-pin-operator]: https://hexdocs.pm/elixir/pattern-matching.html#the-pin-operator +[pattern-matching]: https://en.wikibooks.org/wiki/Haskell/Pattern_matching diff --git a/concepts/pattern-matching/introduction.md b/concepts/pattern-matching/introduction.md index 7ce106582..32700e140 100644 --- a/concepts/pattern-matching/introduction.md +++ b/concepts/pattern-matching/introduction.md @@ -1,55 +1,89 @@ -# Introduction +# About -While `if/else` expressions can be used to execute conditional logic, Haskell also has a more powerful way to execute conditional logic: [pattern matching][pattern-matching]. -With pattern matching, a value can be tested against one or more _patterns_. -An example of such a pattern is the _constant pattern_, which matches a value against a constant (e.g. `1` or `"hello"`). +When writing Haskell functions, we can make use of [pattern matching][pattern-matching]. +Pattern matching is a very powerful feature of Haskell that allows you to match on data constructors and bind variables to the values inside. +As the name suggests, what we do is match values against patterns, and if the value matches the pattern, we can bind variables to the values inside the pattern. +Pattern matching is mainly built of these concepts: recognizing values, binding variables, and breaking down values. -When defining functions, you can define separate function bodies for different patterns. -This leads to clean code that is simple and readable. -You can pattern match on any data type — numbers, characters, lists, tuples, etc. +~~~~exercism/note +Pattern matching in languages such as Elixir is a bit different from pattern matching in Haskell. +Elixir for example allows multiple of same binding names in a pattern, while Haskell does not. +~~~~ -For example, a trivial function that takes a whole number (`Int`) and makes it _1_ closer to _0_ could be expressed like this: + +Take this function for example: + +```haskell +lucky :: Int -> String +lucky 7 = "Lucky number seven!" +lucky x = "Sorry, you're out of luck, pal!" +``` + +Here we have a function `lucky` that takes an `Int` and returns a `String`. +We have defined two patterns for the function, one that matches the number `7` and one that matches any other number, the name can be anything (as long as it follows Haskell naming convention), but we use `x` here. +If the number is `7`, the function will return `"Lucky number seven!"`, otherwise it will return `"Sorry, you're out of luck, pal!"`. +What is important to note here is that the patterns are checked from top to bottom, so if we had swapped the order of the patterns, the function would always return `"Lucky number seven!"`. + +## List patterns + +A very common pattern is to match on lists, and that is taking the head and the tail of the list. +This is due to lists nature of being a linked list. +Here is an example of a function that returns the head and the tail of a list: ```haskell -closerToZero :: Int -> Int -closerToZero 0 = 0 -closerToZero 1 = 0 +headAndTail :: [Int] -> (Int, [Int]) +headAndTail [] = error "Can't call head on an empty list" +headAndTail (x:xs) = (x, xs) ``` -Pattern matching starts to shine when used together with other patterns, for example the _variable pattern_: +We have two patterns here, one that matches an empty list and one that matches a list with at least one element. +This is due to if the list is empty, we need to have a case for that, otherwise we would get a runtime error. +If the list is not empty, we can match the head of the list with `x` and the tail of the list with `xs`. +This is done using the `:` (cons) operator, which is used to prepend an element to a list. +But in pattern matching it allows us to break down a list into its head and tail, so in a way doing the opposite. + +The `xs` is a common name for the tail of a list, it highlights that it is a list, but if you would be working with a nested list, you could use `xss` to highlight that it is a list of lists. + +## Tuple patterns + +As with lists, we can also match on tuples. +Here is an example of a function that takes a tuple and returns the first and second element: ```haskell -closerToZero :: Int -> Int -closerToZero 0 = 0 -closerToZero n = n - 1 +sumTuple :: (Int, Int) -> Int +sumTuple (x, y) = x + y ``` -The above example treats all inputs other than _0_ the same, and would produce incorrect results for negative numbers. -This can be solved using conditional patterns, known as _guards_, which are expressed with the `|` symbol: +Here we have a pattern that matches a tuple with two elements, the first element is bound to `x` and the second element is bound to `y`. + +## Wildcard patterns + +Sometimes we don't care about the value of a variable, we just want to match on the pattern. +This is where the wildcard pattern comes in, it is denoted by an underscore `_`. + +Here is an example of a function that returns the first element of a list: ```haskell -closerToZero :: Int -> Int -closerToZero n - | n < 0 = n + 1 - | n > 0 = n - 1 +head' :: [Int] -> Int +head' [] = error "Can't call head on an empty list" +head' (x:_) = x ``` -In the above examples not all possible inputs have a matching pattern. -The compiler will detect this and output a warning. -This is a very useful feature of Haskell that helps ensure all possible paths are covered to avoid run-time errors. -It is known as _exhaustive checking_. -To solve the warning, you have to handle all cases. -Within _guards_ you can use the expression `otherwise` as syntactic sugar for `True` to catch all remaining patterns. +Here we say we don't need the tail of the list, so we use the wildcard pattern to ignore it. + +## Type patterns + +We can also match on types, this is done by using the constructor of said type. +We can also extract values from the type, like we did with tuples. ```haskell -closerToZero :: Int -> Int -closerToZero n - | n < 0 = n + 1 - | n > 0 = n - 1 - | otherwise = 0 +data AccountType = Guest | User String + +greet :: AccountType -> String +greet Guest = "Welcome, guest!" +greet (User name) = "Welcome, " ++ name ++ "!" ``` -Pattern matching will test a value against each pattern from top to bottom, until it finds a matching pattern and executes the logic associated with that pattern. -**The order of patterns matters!** +In the first pattern we match on the `Guest` constructor, and in the second pattern we match on the `User` constructor and bind the value inside to `name`. -[pattern-matching]: https://learnyouahaskell.github.io/syntax-in-functions#pattern-matching +[pattern-matching]: https://en.wikibooks.org/wiki/Haskell/Pattern_matching diff --git a/concepts/pattern-matching/links.json b/concepts/pattern-matching/links.json index f0c7e057a..458691411 100644 --- a/concepts/pattern-matching/links.json +++ b/concepts/pattern-matching/links.json @@ -1,10 +1,6 @@ [ { - "url": "https://learnyouahaskell.github.io/syntax-in-functions", - "description": "Pattern matching syntax in functions" - }, - { - "url": "https://learnyouahaskell.github.io/syntax-in-functions#guards-guards", - "description": "Use of guards for pattern matching" + "url": "https://en.wikibooks.org/wiki/Haskell/Pattern_matching", + "description": "Wikibooks: matching" } ] diff --git a/concepts/tuple/.meta/config.json b/concepts/tuple/.meta/config.json new file mode 100644 index 000000000..942dada74 --- /dev/null +++ b/concepts/tuple/.meta/config.json @@ -0,0 +1,6 @@ +{ + "authors": [ + "meatball133" + ], + "blurb": "A tuple is a data structure which organizes data, holding a fixed number of items of any type, but without explicit names for each element. Tuples are ideal for storing related information together, but not for storing collections of items that need iterating or might grow or shrink." +} diff --git a/concepts/tuple/about.md b/concepts/tuple/about.md new file mode 100644 index 000000000..373505500 --- /dev/null +++ b/concepts/tuple/about.md @@ -0,0 +1,27 @@ +# About + +[Tuples][tuple] are used commonly to group information, they differ from lists in that they are fixed-size and can hold different data types. +Tuples are created using curly braces, `()`, and are often used to return multiple values from a function. + +```haskell +tuple = (1, "abc", False) +``` + +This can be useful when you for example want to store a coordinate in a 2D space, then you know that the tuple will always have two elements, the x and y coordinate. + +```haskell +coordinate = (3, 4) +``` + +## Accessing elements + +Quite often you work with short tuples, and you can access the elements using the `fst` and `snd` functions. + +```haskell +x = fst coordinate +-- x = 3 +y = snd coordinate +-- y = 4 +``` + +[tuple]: https://hackage.haskell.org/package/base/docs/Data-Tuple.html diff --git a/concepts/tuple/introduction.md b/concepts/tuple/introduction.md new file mode 100644 index 000000000..373505500 --- /dev/null +++ b/concepts/tuple/introduction.md @@ -0,0 +1,27 @@ +# About + +[Tuples][tuple] are used commonly to group information, they differ from lists in that they are fixed-size and can hold different data types. +Tuples are created using curly braces, `()`, and are often used to return multiple values from a function. + +```haskell +tuple = (1, "abc", False) +``` + +This can be useful when you for example want to store a coordinate in a 2D space, then you know that the tuple will always have two elements, the x and y coordinate. + +```haskell +coordinate = (3, 4) +``` + +## Accessing elements + +Quite often you work with short tuples, and you can access the elements using the `fst` and `snd` functions. + +```haskell +x = fst coordinate +-- x = 3 +y = snd coordinate +-- y = 4 +``` + +[tuple]: https://hackage.haskell.org/package/base/docs/Data-Tuple.html diff --git a/concepts/tuple/links.json b/concepts/tuple/links.json new file mode 100644 index 000000000..bceaf2c9e --- /dev/null +++ b/concepts/tuple/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://en.wikibooks.org/wiki/Haskell/Lists_and_tuples#Tuples", + "description": "Wikibooks: Haskell/Lists and tuples" + }, + { + "url": "https://hackage.haskell.org/package/base/docs/Data-Tuple.html", + "description": "Hackae: Data.Tuple" + } +] diff --git a/config.json b/config.json index 145ade90f..e9b04a180 100644 --- a/config.json +++ b/config.json @@ -96,6 +96,19 @@ "concepts": [ "algebraic-data-types" ] + }, + { + "slug": "kitchen-calculator", + "name": "Kitchen Calculator", + "uuid": "d5ca4a8a-9c7f-400c-95fb-764bd655480e", + "prerequisites": [ + "numbers" + ], + "status": "beta", + "concepts": [ + "tuples", + "pattern-matching" + ] } ], "practice": [ @@ -1408,6 +1421,16 @@ "uuid": "cb756cfd-9b5c-408d-bf3f-b2adc8322da3", "slug": "algebraic-data-types", "name": "Algebraic Data Types" + }, + { + "uuid": "4c2ef692-3fb8-46c5-b047-b037449e33ce", + "slug": "tuples", + "name": "Tuples" + }, + { + "uuid": "5bda814e-0dec-4952-ab62-44b1751bf6cc", + "slug": "pattern-matching", + "name": "Pattern Matching" } ], "key_features": [ diff --git a/exercises/concept/KitchenCalculator/.docs/hints.md b/exercises/concept/KitchenCalculator/.docs/hints.md new file mode 100644 index 000000000..12ee8a7b3 --- /dev/null +++ b/exercises/concept/KitchenCalculator/.docs/hints.md @@ -0,0 +1,30 @@ +# Hints + +## General + +- [Tuples][tuple-module] are data structures which are arranged in contiguous memory and can hold any data-type. +- Atoms may be used to denote finite states, as this exercise uses `:cup`, `:fluid_ounce`, `:teaspoon`, `:tablespoon`, `:milliliter` to denote the units used. +- You may use [`Kernel`][elem] or [`Tuple`][tuple-module] functions or pattern matching to manipulate the tuples. + +## 1. Get the numeric component from a volume-pair + +- Consider using [a `Kernel` module function][elem] to return the volume-pair's numeric component. + +## 2. Convert the volume-pair to milliliters + +- Use [multiple clause functions][multi-clause] and [pattern matching][pattern-matching] to reduce conditional control flow logic. +- Implement the function for all units to milliliters, including milliliters to milliliters. + +## 3. Convert the milliliter volume-pair to another unit + +- Use [multiple clause functions][multi-clause] and [pattern matching][pattern-matching] to reduce conditional control flow logic. +- Implement the function for milliliters to all units, including milliliters to milliliters. + +## 4. Convert from any unit to any unit + +- Reuse the functions already created to perform the conversion in the `convert/2` function. + +[elem]: https://hexdocs.pm/elixir/Kernel.html#elem/2 +[multi-clause]: https://hexdocs.pm/elixir/modules-and-functions.html#function-definition +[tuple-module]: https://hexdocs.pm/elixir/Tuple.html +[pattern-matching]: https://medium.com/rebirth-delivery/how-to-use-elixir-pattern-matched-functions-arguments-a793733acc6d diff --git a/exercises/concept/KitchenCalculator/.docs/instructions.md b/exercises/concept/KitchenCalculator/.docs/instructions.md new file mode 100644 index 000000000..f0e1b9e9e --- /dev/null +++ b/exercises/concept/KitchenCalculator/.docs/instructions.md @@ -0,0 +1,57 @@ +# Instructions + +While preparing to bake cookies for your friends, you have found that you have to convert some of the measurements used in the recipe. +Being only familiar with the metric system, you need to come up with a way to convert common US baking measurements to milliliters (mL) for your own ease. + +Use this conversion chart for your solution: + +| Unit to convert | volume | in milliliters (mL) | +| --------------- | ------ | ------------------- | +| mL | 1 | 1 | +| US cup | 1 | 240 | +| US fluid ounce | 1 | 30 | +| US teaspoon | 1 | 5 | +| US tablespoon | 1 | 15 | + +Being a talented programmer in training, you decide to use milliliters as a transition unit to facilitate the conversion from any unit listed to any other (even itself). + +## 1. Get the numeric component from a volume-pair + +Implement the `getVolume` function. Given a volume-pair tuple, it should return just the numeric component. + +```haskell +getVolume (Cup, 2.0) +-- -> 2.0 +``` + +## 2. Convert the volume-pair to milliliters + +Implement the `toMilliliter` function. Given a volume-pair tuple, it should convert the volume to milliliters using the conversion chart. + +Use multiple function clauses and pattern matching to create the functions for each unit. The atoms used to denote each unit are: `Cup`, `FluidOunce`, `Teaspoon`, `Tablespoon`, `Milliliter`. +Return the result of the conversion wrapped in a tuple. + +```haskell +toMilliliter (Cup, 2.5) +-- -> (Milliliter, 600.0) +``` + +## 3. Convert the milliliter volume-pair to another unit + +Implement the `fromMilliliter` function. Given a volume-pair tuple and the desired unit, it should convert the volume to the desired unit using the conversion chart. + +Use multiple function clauses and pattern matching to create the functions for each unit. The atoms used to denote each unit are: `Cup`, `FluidOunce`, `Teaspoon`, `Tablespoon`, `Milliliter`. + +```haskell +fromMilliliter (Milliliter, 1320.0) Cup +-- -> (Cup, 5.5) +``` + +## 4. Convert from any unit to any unit + +Implement the `convert` function. Given a volume-pair tuple and the desired unit, it should convert the given volume to the desired unit. + +```haskell +convert (Teaspoon, 9.0) Tablespoon +-- -> (Tablespoon, 3.0) +``` diff --git a/exercises/concept/KitchenCalculator/.docs/introduction.md b/exercises/concept/KitchenCalculator/.docs/introduction.md new file mode 100644 index 000000000..c73cb19ed --- /dev/null +++ b/exercises/concept/KitchenCalculator/.docs/introduction.md @@ -0,0 +1,109 @@ +## Tuple + +[Tuples][tuple] are used commonly to group information, they differ from lists in that they are fixed-size and can hold different data types. +Tuples are created using curly braces, `()`, and are often used to return multiple values from a function. + +```haskell +tuple = (1, "abc", False) +``` + +This can be useful when you for example want to store a coordinate in a 2D space, then you know that the tuple will always have two elements, the x and y coordinate. + +```haskell +coordinate = (3, 4) +``` + +### Accessing elements + +Quite often you work with short tuples, and you can access the elements using the `fst` and `snd` functions. + +```haskell +x = fst coordinate +-- x = 3 +y = snd coordinate +-- y = 4 +``` + +## Pattern matching + +When writing Haskell functions, we can make use of an [assertive style][assertive-style] with [pattern matching][pattern-match-doc]: +Pattern matching is a very powerful feature of Haskell that allows you to match on data constructors and bind variables to the values inside. +As the name suggests, what we do is match values against patterns, and if the value matches the pattern, we can bind variables to the values inside the pattern. +Pattern matching is mainly built of these concepts: recognizing values, binding variables, and breaking down values. + +Take this function for example: + +```haskell +lucky :: Int -> String +lucky 7 = "Lucky number seven!" +lucky x = "Sorry, you're out of luck, pal!" +``` + +Here we have a function `lucky` that takes an `Int` and returns a `String`. +We have defined two patterns for the function, one that matches the number `7` and one that matches any other number, the name can be anything (as long as it follows Haskell naming convention), but we use `x` here. +If the number is `7`, the function will return `"Lucky number seven!"`, otherwise it will return `"Sorry, you're out of luck, pal!"`. +What is important to note here is that the patterns are checked from top to bottom, so if we had swapped the order of the patterns, the function would always return `"Lucky number seven!"`. + +### List patterns + +A very common pattern is to match on lists, and that is taking the head and the tail of the list. +This is due to lists nature of being a linked list. +Here is an example of a function that returns the head and the tail of a list: + +```haskell +headAndTail :: [Int] -> (Int, [Int]) +headAndTail [] = error "Can't call head on an empty list" +headAndTail (x:xs) = (x, xs) +``` + +We have two patterns here, one that matches an empty list and one that matches a list with at least one element. +This is due to if the list is empty, we need to have a case for that, otherwise we would get a runtime error. +If the list is not empty, we can match the head of the list with `x` and the tail of the list with `xs`. +This is done using the `:` (cons) operator, which is used to prepend an element to a list. +But in pattern matching it allows us to break down a list into its head and tail, so in a way doing the opposite. + +The `xs` is a common name for the tail of a list, it highlights that it is a list, but if you would be working with a nested list, you could use `xss` to highlight that it is a list of lists. + +### Tuple patterns + +As with lists, we can also match on tuples. +Here is an example of a function that takes a tuple and returns the first and second element: + +```haskell +sumTuple :: (Int, Int) -> Int +sumTuple (x, y) = x + y +``` + +Here we have a pattern that matches a tuple with two elements, the first element is bound to `x` and the second element is bound to `y`. + +### Wildcard patterns + +Sometimes we don't care about the value of a variable, we just want to match on the pattern. +This is where the wildcard pattern comes in, it is denoted by an underscore `_`. + +Here is an example of a function that returns the first element of a list: + +```haskell +head' :: [Int] -> Int +head' [] = error "Can't call head on an empty list" +head' (x:_) = x +``` + +Here we say we don't need the tail of the list, so we use the wildcard pattern to ignore it. + +### Type patterns + +We can also match on types, this is done by using the constructor of said type. +We can also extract values from the type, like we did with tuples. + +```haskell +data AccountType = Guest | User String + +greet :: AccountType -> String +greet Guest = "Welcome, guest!" +greet (User name) = "Welcome, " ++ name ++ "!" +``` + +In the first pattern we match on the `Guest` constructor, and in the second pattern we match on the `User` constructor and bind the value inside to `name`. + +[tuple]: https://hackage.haskell.org/package/base/docs/Data-Tuple.html diff --git a/exercises/concept/KitchenCalculator/.meta/config.json b/exercises/concept/KitchenCalculator/.meta/config.json new file mode 100644 index 000000000..a531f2b51 --- /dev/null +++ b/exercises/concept/KitchenCalculator/.meta/config.json @@ -0,0 +1,25 @@ +{ + "authors": [ + "meatball133" + ], + "files": { + "solution": [ + "src/KitchenCalculator.hs", + "package.yaml" + ], + "test": [ + "test/Tests.hs" + ], + "exemplar": [ + ".meta/exemplar/src/KitchenCalculator.hs" + ], + "invalidator": [ + "stack.yaml" + ] + }, + "forked_from": [ + "elixir/kitchen-calculator" + ], + "icon": "kitchen-calculator", + "blurb": "Learn about tuples and pattern matching by converting common US baking measurements to the metric system." +} diff --git a/exercises/concept/KitchenCalculator/.meta/design.md b/exercises/concept/KitchenCalculator/.meta/design.md new file mode 100644 index 000000000..936dacd90 --- /dev/null +++ b/exercises/concept/KitchenCalculator/.meta/design.md @@ -0,0 +1,34 @@ +# Design + +## Learning objectives + +- Know what a tuple is +- Know how to write a tuple literal +- Know how to return a tuple from a function +- Know that returning a tuple is a common for of multiple return in Haskell +- Know how to extract an element from a tuple using pattern matching +- Know how to perform a basic pattern match using the `=` function +- Know how to perform basic pattern matching on function arguments to guide which function to invoke + +## Out of scope + +- case do pattern matching + +## Prerequisites + +- `type declartion`: needs to be able to define and use atoms for return values +- `multiple-clause-functions`: need to know that a named function can be overloaded and Elixir will attempt to use them all until one found +- `floating-point-numbers`: need to know how to use floating point numbers to convert + +## Concepts + +- `tuples` know of the existence of the `tuple` data type, how to define tuple literals, extract values from a tuple using `elem/2` +- `pattern-matching` basic knowledge of pattern matching using `=/2` and on function arguments + +## Analyzer + +This exercise could benefit from the following rules added to the the [analyzer][analyzer]: + +- Verify common conversion functions aren't used from the standard library if they exist. + +[analyzer]: https://github.com/exercism/elixir-analyzer \ No newline at end of file diff --git a/exercises/concept/KitchenCalculator/.meta/exemplar/package.yaml b/exercises/concept/KitchenCalculator/.meta/exemplar/package.yaml new file mode 100644 index 000000000..2384fa8ea --- /dev/null +++ b/exercises/concept/KitchenCalculator/.meta/exemplar/package.yaml @@ -0,0 +1,18 @@ +name: lucians-luscious-lasagna +version: 1.0.0.0 + +dependencies: + - base + +library: + exposed-modules: LuciansLusciousLasagna + source-dirs: src + ghc-options: -Wall + +tests: + test: + main: Tests.hs + source-dirs: test + dependencies: + - lucians-luscious-lasagna + - hspec diff --git a/exercises/concept/KitchenCalculator/.meta/exemplar/src/KitchenCalculator.hs b/exercises/concept/KitchenCalculator/.meta/exemplar/src/KitchenCalculator.hs new file mode 100644 index 000000000..88dcabd71 --- /dev/null +++ b/exercises/concept/KitchenCalculator/.meta/exemplar/src/KitchenCalculator.hs @@ -0,0 +1,24 @@ +module KitchenCalculator (getVolume, toMilliliter, fromMilliliter, convert, Messurments (..)) where + +data Messurments = Cup | FluidOnce | TableSpoon | TeaSpoon | Milliliter deriving (Eq, Show) + +getVolume :: (Messurments, Double) -> Double +getVolume (_, n) = n + +toMilliliter :: (Messurments, Double) -> (Messurments, Double) +toMilliliter (Cup, n) = (Milliliter, n * 240) +toMilliliter (FluidOnce, n) = (Milliliter, n * 30) +toMilliliter (TableSpoon, n) = (Milliliter, n * 15) +toMilliliter (TeaSpoon, n) = (Milliliter, n * 5) +toMilliliter valuePair = valuePair + + +fromMilliliter :: (Messurments, Double) -> Messurments -> (Messurments, Double) +fromMilliliter (_, n) Milliliter = (Milliliter, n) +fromMilliliter (_, n) Cup = (Cup, n / 240) +fromMilliliter (_, n) FluidOnce = (FluidOnce, n / 30) +fromMilliliter (_, n) TableSpoon = (TableSpoon, n / 15) +fromMilliliter (_, n) TeaSpoon = (TeaSpoon, n / 5) + +convert :: (Messurments, Double) -> Messurments -> (Messurments, Double) +convert (m, n) to = fromMilliliter (toMilliliter (m, n)) to diff --git a/exercises/concept/KitchenCalculator/package.yaml b/exercises/concept/KitchenCalculator/package.yaml new file mode 100644 index 000000000..12988a896 --- /dev/null +++ b/exercises/concept/KitchenCalculator/package.yaml @@ -0,0 +1,18 @@ +name: kitchen-calculator +version: 1.0.0.0 + +dependencies: + - base + +library: + exposed-modules: KitchenCalculator + source-dirs: src + ghc-options: -Wall + +tests: + test: + main: Tests.hs + source-dirs: test + dependencies: + - kitchen-calculator + - hspec diff --git a/exercises/concept/KitchenCalculator/src/KitchenCalculator.hs b/exercises/concept/KitchenCalculator/src/KitchenCalculator.hs new file mode 100644 index 000000000..61fe46865 --- /dev/null +++ b/exercises/concept/KitchenCalculator/src/KitchenCalculator.hs @@ -0,0 +1,15 @@ +module KitchenCalculator (getVolume, toMilliliter, fromMilliliter, convert, Messurments (..)) where + +data Messurments = Cup | FluidOnce | TableSpoon | TeaSpoon | Milliliter deriving (Eq, Show) + +getVolume :: (Messurments, Double) -> Double +getVolume = error "Implement this function." + +toMilliliter :: (Messurments, Double) -> (Messurments, Double) +toMilliliter = error "Implement this function." + +fromMilliliter :: (Messurments, Double) -> Messurments -> (Messurments, Double) +fromMilliliter = error "Implement this function." + +convert :: (Messurments, Double) -> Messurments -> (Messurments, Double) +convert = error "Implement this function." diff --git a/exercises/concept/KitchenCalculator/stack.yaml b/exercises/concept/KitchenCalculator/stack.yaml new file mode 100644 index 000000000..115878212 --- /dev/null +++ b/exercises/concept/KitchenCalculator/stack.yaml @@ -0,0 +1 @@ +resolver: lts-20.18 diff --git a/exercises/concept/KitchenCalculator/test/Tests.hs b/exercises/concept/KitchenCalculator/test/Tests.hs new file mode 100644 index 000000000..b53b8cb19 --- /dev/null +++ b/exercises/concept/KitchenCalculator/test/Tests.hs @@ -0,0 +1,61 @@ +import Test.Hspec (it, shouldBe, hspec) +import KitchenCalculator (getVolume, toMilliliter, fromMilliliter, convert, Messurments(..)) + +main :: IO () +main = hspec $ do + it "get cups" $ do + getVolume (Cup, 1) `shouldBe` 1 + + it "get fluid ounces" $ do + getVolume (FluidOnce, 2) `shouldBe` 2 + + it "get tablespoons" $ do + getVolume (TableSpoon, 3) `shouldBe` 3 + + it "get teaspoons" $ do + getVolume (TeaSpoon, 4) `shouldBe` 4 + + it "get milliliters" $ do + getVolume (Milliliter, 5) `shouldBe` 5 + + it "convert milliliters to milliliters" $ do + toMilliliter (Milliliter, 3) `shouldBe` (Milliliter, 3) + + it "convert cups to milliliters" $ do + toMilliliter (Cup, 3) `shouldBe` (Milliliter, 720) + + it "convert fluid ounces to milliliters" $ do + toMilliliter (FluidOnce, 100) `shouldBe` (Milliliter, 3000) + + it "convert tablespoons to milliliters" $ do + toMilliliter (TableSpoon, 3) `shouldBe` (Milliliter, 45) + + it "convert teaspoons to milliliters" $ do + toMilliliter (TeaSpoon, 3) `shouldBe` (Milliliter, 15) + + it "convert from milliliters to milliliters" $ do + fromMilliliter (Milliliter, 4) Milliliter `shouldBe` (Milliliter, 4) + + it "convert from milliliters to cups" $ do + fromMilliliter (Milliliter, 840) Cup `shouldBe` (Cup, 3.5) + + it "convert from milliliters to fluid ounces" $ do + fromMilliliter (Milliliter, 4522.5) FluidOnce `shouldBe` (FluidOnce, 150.75) + + it "convert from milliliters to tablespoons" $ do + fromMilliliter (Milliliter, 71.25) TableSpoon `shouldBe` (TableSpoon, 4.75) + + it "convert from milliliters to teaspoons" $ do + fromMilliliter (Milliliter, 61.25) TeaSpoon `shouldBe` (TeaSpoon, 12.25) + + it "teaspoon to tablespoon" $ do + convert (TeaSpoon, 15) TableSpoon `shouldBe` (TableSpoon, 5) + + it "cups to fluid ounces" $ do + convert (Cup, 4) FluidOnce `shouldBe` (FluidOnce, 32) + + it "fluid ounces to teaspoons" $ do + convert (FluidOnce, 4) TeaSpoon `shouldBe` (TeaSpoon, 24) + + it "tablespoons to cups" $ do + convert (TableSpoon, 320) Cup `shouldBe` (Cup, 20) From 09a04270303b2ef109882474d53d6bdde546c6f6 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 5 Oct 2024 19:24:29 +0200 Subject: [PATCH 3/5] Rename folder --- .../{KitchenCalculator => kitchen-calculator}/.docs/hints.md | 0 .../.docs/instructions.md | 0 .../.docs/introduction.md | 0 .../{KitchenCalculator => kitchen-calculator}/.meta/config.json | 0 .../{KitchenCalculator => kitchen-calculator}/.meta/design.md | 0 .../.meta/exemplar/package.yaml | 0 .../.meta/exemplar/src/KitchenCalculator.hs | 0 .../{KitchenCalculator => kitchen-calculator}/package.yaml | 0 .../src/KitchenCalculator.hs | 0 .../concept/{KitchenCalculator => kitchen-calculator}/stack.yaml | 0 .../{KitchenCalculator => kitchen-calculator}/test/Tests.hs | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/.docs/hints.md (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/.docs/instructions.md (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/.docs/introduction.md (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/.meta/config.json (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/.meta/design.md (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/.meta/exemplar/package.yaml (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/.meta/exemplar/src/KitchenCalculator.hs (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/package.yaml (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/src/KitchenCalculator.hs (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/stack.yaml (100%) rename exercises/concept/{KitchenCalculator => kitchen-calculator}/test/Tests.hs (100%) diff --git a/exercises/concept/KitchenCalculator/.docs/hints.md b/exercises/concept/kitchen-calculator/.docs/hints.md similarity index 100% rename from exercises/concept/KitchenCalculator/.docs/hints.md rename to exercises/concept/kitchen-calculator/.docs/hints.md diff --git a/exercises/concept/KitchenCalculator/.docs/instructions.md b/exercises/concept/kitchen-calculator/.docs/instructions.md similarity index 100% rename from exercises/concept/KitchenCalculator/.docs/instructions.md rename to exercises/concept/kitchen-calculator/.docs/instructions.md diff --git a/exercises/concept/KitchenCalculator/.docs/introduction.md b/exercises/concept/kitchen-calculator/.docs/introduction.md similarity index 100% rename from exercises/concept/KitchenCalculator/.docs/introduction.md rename to exercises/concept/kitchen-calculator/.docs/introduction.md diff --git a/exercises/concept/KitchenCalculator/.meta/config.json b/exercises/concept/kitchen-calculator/.meta/config.json similarity index 100% rename from exercises/concept/KitchenCalculator/.meta/config.json rename to exercises/concept/kitchen-calculator/.meta/config.json diff --git a/exercises/concept/KitchenCalculator/.meta/design.md b/exercises/concept/kitchen-calculator/.meta/design.md similarity index 100% rename from exercises/concept/KitchenCalculator/.meta/design.md rename to exercises/concept/kitchen-calculator/.meta/design.md diff --git a/exercises/concept/KitchenCalculator/.meta/exemplar/package.yaml b/exercises/concept/kitchen-calculator/.meta/exemplar/package.yaml similarity index 100% rename from exercises/concept/KitchenCalculator/.meta/exemplar/package.yaml rename to exercises/concept/kitchen-calculator/.meta/exemplar/package.yaml diff --git a/exercises/concept/KitchenCalculator/.meta/exemplar/src/KitchenCalculator.hs b/exercises/concept/kitchen-calculator/.meta/exemplar/src/KitchenCalculator.hs similarity index 100% rename from exercises/concept/KitchenCalculator/.meta/exemplar/src/KitchenCalculator.hs rename to exercises/concept/kitchen-calculator/.meta/exemplar/src/KitchenCalculator.hs diff --git a/exercises/concept/KitchenCalculator/package.yaml b/exercises/concept/kitchen-calculator/package.yaml similarity index 100% rename from exercises/concept/KitchenCalculator/package.yaml rename to exercises/concept/kitchen-calculator/package.yaml diff --git a/exercises/concept/KitchenCalculator/src/KitchenCalculator.hs b/exercises/concept/kitchen-calculator/src/KitchenCalculator.hs similarity index 100% rename from exercises/concept/KitchenCalculator/src/KitchenCalculator.hs rename to exercises/concept/kitchen-calculator/src/KitchenCalculator.hs diff --git a/exercises/concept/KitchenCalculator/stack.yaml b/exercises/concept/kitchen-calculator/stack.yaml similarity index 100% rename from exercises/concept/KitchenCalculator/stack.yaml rename to exercises/concept/kitchen-calculator/stack.yaml diff --git a/exercises/concept/KitchenCalculator/test/Tests.hs b/exercises/concept/kitchen-calculator/test/Tests.hs similarity index 100% rename from exercises/concept/KitchenCalculator/test/Tests.hs rename to exercises/concept/kitchen-calculator/test/Tests.hs From e46aa84cacb73f6a823e549dba581c01faa16d64 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 5 Oct 2024 19:27:21 +0200 Subject: [PATCH 4/5] Rename tuple concept --- concepts/{tuple => tuples}/.meta/config.json | 0 concepts/{tuple => tuples}/about.md | 0 concepts/{tuple => tuples}/introduction.md | 0 concepts/{tuple => tuples}/links.json | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename concepts/{tuple => tuples}/.meta/config.json (100%) rename concepts/{tuple => tuples}/about.md (100%) rename concepts/{tuple => tuples}/introduction.md (100%) rename concepts/{tuple => tuples}/links.json (100%) diff --git a/concepts/tuple/.meta/config.json b/concepts/tuples/.meta/config.json similarity index 100% rename from concepts/tuple/.meta/config.json rename to concepts/tuples/.meta/config.json diff --git a/concepts/tuple/about.md b/concepts/tuples/about.md similarity index 100% rename from concepts/tuple/about.md rename to concepts/tuples/about.md diff --git a/concepts/tuple/introduction.md b/concepts/tuples/introduction.md similarity index 100% rename from concepts/tuple/introduction.md rename to concepts/tuples/introduction.md diff --git a/concepts/tuple/links.json b/concepts/tuples/links.json similarity index 100% rename from concepts/tuple/links.json rename to concepts/tuples/links.json From 43dde8dae34933dee1b4ebe3e001d54e8c02c189 Mon Sep 17 00:00:00 2001 From: meatball Date: Thu, 10 Oct 2024 22:14:49 +0200 Subject: [PATCH 5/5] More work --- concepts/guards/.meta/config.json | 6 ++++ concepts/guards/about.md | 30 +++++++++++++++++++ concepts/guards/introduction.md | 30 +++++++++++++++++++ concepts/guards/links.json | 1 + .../.meta/exemplar/src/GuessingGame.hs | 13 ++++---- 5 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 concepts/guards/.meta/config.json create mode 100644 concepts/guards/about.md create mode 100644 concepts/guards/introduction.md create mode 100644 concepts/guards/links.json diff --git a/concepts/guards/.meta/config.json b/concepts/guards/.meta/config.json new file mode 100644 index 000000000..7ffe8f6c1 --- /dev/null +++ b/concepts/guards/.meta/config.json @@ -0,0 +1,6 @@ +{ + "authors": [ + "meatball133" + ], + "blurb": "Guards are used to prevent function invocation based on evaluation of the arguments by guard functions. Guards begin with the `|` operatpr, followed by a boolean expression. Guards are used to augment pattern matching with more complex checks." +} diff --git a/concepts/guards/about.md b/concepts/guards/about.md new file mode 100644 index 000000000..5893b3a54 --- /dev/null +++ b/concepts/guards/about.md @@ -0,0 +1,30 @@ +# About + +[Guards][guards] are used as a complement to [pattern matching][exercism-pattern-matching]. +Which we will cover in a later concept. +Guards allows us to have different implementations of a function depending on the value of the input. +They allow for more complex checks. + +A guard statement is defined by a pipe `|` followed by a boolean expression, ending with an equal sign `=` and the functions body. +There can be multiple guard statements for a single function. +The guard statements is evaluated from top to bottom, and the first one that evaluates to `True` will be executed. +A guard statement allows for a `otherwise` statement, which is a catch-all for any value that doesn't match the previous guard statements. + +```haskell +isEven :: Int -> String +isEven n + | even n = "n is even" + | otherwise = "n is odd" +``` + +We can also deffine our function and use it inside the guard statement. + +```haskell +isEven' :: Int -> Bool +isEven' n = even n + +isEven :: Int -> String +isEven n + | isEven' n = "n is even" + | otherwise = "n is odd" +``` diff --git a/concepts/guards/introduction.md b/concepts/guards/introduction.md new file mode 100644 index 000000000..5893b3a54 --- /dev/null +++ b/concepts/guards/introduction.md @@ -0,0 +1,30 @@ +# About + +[Guards][guards] are used as a complement to [pattern matching][exercism-pattern-matching]. +Which we will cover in a later concept. +Guards allows us to have different implementations of a function depending on the value of the input. +They allow for more complex checks. + +A guard statement is defined by a pipe `|` followed by a boolean expression, ending with an equal sign `=` and the functions body. +There can be multiple guard statements for a single function. +The guard statements is evaluated from top to bottom, and the first one that evaluates to `True` will be executed. +A guard statement allows for a `otherwise` statement, which is a catch-all for any value that doesn't match the previous guard statements. + +```haskell +isEven :: Int -> String +isEven n + | even n = "n is even" + | otherwise = "n is odd" +``` + +We can also deffine our function and use it inside the guard statement. + +```haskell +isEven' :: Int -> Bool +isEven' n = even n + +isEven :: Int -> String +isEven n + | isEven' n = "n is even" + | otherwise = "n is odd" +``` diff --git a/concepts/guards/links.json b/concepts/guards/links.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/concepts/guards/links.json @@ -0,0 +1 @@ +[] diff --git a/exercises/concept/guessing-game/.meta/exemplar/src/GuessingGame.hs b/exercises/concept/guessing-game/.meta/exemplar/src/GuessingGame.hs index 9f0c96d05..b111a9e23 100644 --- a/exercises/concept/guessing-game/.meta/exemplar/src/GuessingGame.hs +++ b/exercises/concept/guessing-game/.meta/exemplar/src/GuessingGame.hs @@ -1,9 +1,8 @@ module GuessingGame (reply) where -reply :: Int -> String -reply 41 = "So close" -reply 42 = "Correct" -reply 43 = "So close" -reply guess - | guess < 41 = "Too low" - | otherwise = "Too high" +reply :: Int -> Int -> String +reply n guess + | guess == n = "Correct" + | guess + 1 == n || guess - 1 == n = "So close!" + | guess < n = "Too low" + | guess > n = "Too high"