Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add pattern matching and tuple concept #1275

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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/guards/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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."
}
30 changes: 30 additions & 0 deletions concepts/guards/about.md
Original file line number Diff line number Diff line change
@@ -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"
```
30 changes: 30 additions & 0 deletions concepts/guards/introduction.md
Original file line number Diff line number Diff line change
@@ -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"
```
1 change: 1 addition & 0 deletions concepts/guards/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
6 changes: 6 additions & 0 deletions concepts/pattern-matching/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"authors": [
"meatball133"
],
"blurb": "Introduction to pattern matching in Haskell."
}
89 changes: 89 additions & 0 deletions concepts/pattern-matching/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# About

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
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`.

[pattern-matching]: https://en.wikibooks.org/wiki/Haskell/Pattern_matching
89 changes: 89 additions & 0 deletions concepts/pattern-matching/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# About

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
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`.

[pattern-matching]: https://en.wikibooks.org/wiki/Haskell/Pattern_matching
6 changes: 6 additions & 0 deletions concepts/pattern-matching/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"url": "https://en.wikibooks.org/wiki/Haskell/Pattern_matching",
"description": "Wikibooks: matching"
}
]
6 changes: 6 additions & 0 deletions concepts/tuples/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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."
}
27 changes: 27 additions & 0 deletions concepts/tuples/about.md
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions concepts/tuples/introduction.md
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions concepts/tuples/links.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
23 changes: 23 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down Expand Up @@ -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": [
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Loading
Loading