Skip to content

Commit 6432d3e

Browse files
committed
2 parents 9173dde + 45639db commit 6432d3e

30 files changed

+1153
-882
lines changed

Gemfile.lock

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ GEM
99
eventmachine (>= 0.12.9)
1010
http_parser.rb (~> 0)
1111
eventmachine (1.2.7)
12+
ffi (1.17.0-arm64-darwin)
1213
ffi (1.17.0-x64-mingw-ucrt)
14+
ffi (1.17.0-x86_64-linux-gnu)
1315
forwardable-extended (2.6.0)
1416
http_parser.rb (0.8.0)
1517
i18n (1.14.5)
@@ -58,35 +60,38 @@ GEM
5860
jekyll (>= 3.5, < 5.0)
5961
jekyll-feed (~> 0.9)
6062
jekyll-seo-tag (~> 2.1)
63+
nokogiri (1.16.7-arm64-darwin)
64+
racc (~> 1.4)
6165
nokogiri (1.16.7-x64-mingw-ucrt)
6266
racc (~> 1.4)
67+
nokogiri (1.18.8-x86_64-linux-gnu)
68+
racc (~> 1.4)
6369
pathutil (0.16.2)
6470
forwardable-extended (~> 2.6)
6571
public_suffix (6.0.1)
6672
racc (1.8.1)
6773
rb-fsevent (0.11.2)
6874
rb-inotify (0.11.1)
6975
ffi (~> 1.0)
70-
rexml (3.3.6)
71-
strscan
76+
rexml (3.3.9)
7277
rouge (4.3.0)
7378
rubyzip (2.3.2)
7479
safe_yaml (1.0.5)
7580
sassc (2.4.0)
7681
ffi (~> 1.9)
77-
strscan (3.1.0)
7882
terminal-table (3.0.2)
7983
unicode-display_width (>= 1.1.1, < 3)
8084
tzinfo (2.0.6)
8185
concurrent-ruby (~> 1.0)
8286
tzinfo-data (1.2024.1)
8387
tzinfo (>= 1.0.0)
8488
unicode-display_width (2.5.0)
85-
wdm (0.1.1)
86-
webrick (1.8.1)
89+
webrick (1.8.2)
8790

8891
PLATFORMS
92+
arm64-darwin-24
8993
x64-mingw-ucrt
94+
x86_64-linux
9095

9196
DEPENDENCIES
9297
jekyll
@@ -97,7 +102,6 @@ DEPENDENCIES
97102
minima
98103
nokogiri
99104
tzinfo-data
100-
wdm (~> 0.1.0)
101105
webrick
102106

103107
BUNDLED WITH

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,7 @@ After this, the pre-commit hooks will run automatically on each commit to check
2828

2929
### Adding Solutions
3030

31-
If you want to add solutions to your markdown files in this Jekyll site, follow these steps:
32-
33-
### 1. Format Your Solutions Section
34-
35-
Ensure that your solutions are marked with a heading that is only the word "Solutions" This can be at any heading level (e.g., `### Solutions`, `#### Solutions`). For example:
31+
If you want to add solutions to your markdown files in this Jekyll site, you need to separate the solutions into a section. Ensure that your solutions are marked with a heading that is only the word "Solutions". This can be at any heading level (e.g., `### Solutions`, `#### Solutions`). For example:
3632

3733
```markdown
3834
### Solutions
@@ -49,3 +45,24 @@ const exampleFunction = () => {
4945
```
5046

5147
Do not include any subheadings in solutions. The `wrap_solution` plugin, will automatically process this and hide solutions by default, and will be toggleable on any relevant page.
48+
49+
### Adding Alert Boxes
50+
51+
To add an alert box:
52+
53+
```markdown
54+
<div class="alert-box alert-info" markdown="1">
55+
This is an *important* bit of information!
56+
</div>
57+
```
58+
59+
Supported alert boxes are `alert-info`, `alert-warning`, and `alert-danger`. To add a heading to the alert box, put the heading in strong emphasis/bold:
60+
61+
```markdown
62+
<div class="alert-box alert-info" markdown="1">
63+
**Important Information**
64+
This is an *important* bit of information!
65+
</div>
66+
```
67+
68+
The CSS styles will automatically add ‘💡 Note:’, ‘⚠️ Warning:’, or ‘🚨 Important:’ (for the classes `alert-info`, `alert-warning`, and `alert-danger` respectively) in front of the text in bold. Note that this means you cannot use bold text anywhere else in the info box at the moment.

_chapters/asteroids.md

Lines changed: 79 additions & 74 deletions
Large diffs are not rendered by default.

_chapters/eithers.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ layout: chapter
33
title: "Eithers"
44
---
55

6+
<div class="alert-box alert-info" markdown="1">
7+
**This is optional reading.**
8+
</div>
9+
610
## Learning Outcomes
711

8-
- Understand how the `Either` type handles values with two possibilities, typically used for error handling and success cases.
9-
- Apply the `Functor`, `Applicative`, and `Monad` type classes to the `Either` type, learning how to implement instances for each.
10-
- Recognize the power of monadic `do` blocks in simplifying code and handling complex workflows.
12+
- Understand how the `Either` type handles values with two possibilities, typically used for error handling and success cases
13+
- Apply the `Functor`, `Applicative`, and `Monad` type classes to the `Either` type, learning how to implement instances for each
14+
- Recognise the power of monadic `do` blocks in simplifying code and handling complex workflows
1115

1216
## Introduction to Eithers
1317

@@ -17,13 +21,13 @@ In Haskell, the `Either` type is used to represent values with two possibilities
1721
data Either a b = Left a | Right b
1822
```
1923

20-
In Haskells `Either` type, convention ([and the official documentation](https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-Either.html)) says errors go on the `Left` and successes on the `Right`. Why? Because if it is not right (correct) it must be left. This can be considered another example of bias against the left-handed people around the world, but alas, it is a [cruel world](https://www.youtube.com/watch?v=epvlvDzKfv8).
24+
In Haskells `Either` type, convention ([and the official documentation](https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-Either.html)) says errors go on the `Left` and successes on the `Right`. Why? Because if it is not right (correct), it must be left. This can be considered another example of bias against the left-handed people around the world, but alas, it is a [cruel world](https://www.youtube.com/watch?v=epvlvDzKfv8).
2125

22-
The `Left`/`Right` convention is also more general then a `Success`/`Error` naming, as `Left` does not always need to be an error, but it is the most common usage.
26+
The `Left`/`Right` convention is also more general than a `Success`/`Error` naming, as `Left` does not always need to be an error, but it is the most common usage.
2327

2428
## Usage of Either
2529

26-
We can use `Either` to help us with error catching, similar to a `Maybe` type. However, since the error case, has a value, rather than `Nothing`, allowing to store an error message to give information to the programmer/user.
30+
We can use `Either` to help us with error catching, similar to a `Maybe` type. However, the error case has a value rather than `Nothing`, allowing to store an error message to give information to the programmer/user.
2731

2832
```haskell
2933
divide :: Double -> Double -> Either String Double
@@ -61,7 +65,7 @@ Before diving into the `Either` typeclass, let’s briefly recap what kinds are:
6165

6266
The `Functor` type class expects a type of kind `* -> *`. For `Either`, this means partially applying the first type parameter, e.g., `instance Functor (Either a)`, where `a` will be the type of the `Left`.
6367

64-
We can then define, `fmap` over either, considering `Left` as the error case, and applying the function, when we have a correct (`Right`) case.
68+
We can then define `fmap` over `Either`, considering `Left` as the error case, and applying the function when we have a correct (`Right`) case.
6569

6670
```haskell
6771
instance Functor (Either a) where
@@ -135,7 +139,7 @@ import Control.Exception (catch, IOException)
135139

136140
readFileSafe :: FilePath -> IO (Either FileError String)
137141
-- catch any IOException, and use `handleError` on IOException
138-
readFileSafe path = catch (Right <$> (readFile path)) handleError
142+
readFileSafe path = catch (Right <$> readFile path) handleError
139143
where
140144
handleError :: IOException -> IO (Either FileError String)
141145
handleError _ = return $ Left FileReadError
@@ -148,7 +152,6 @@ readData :: String -> Either ReadError [String]
148152
readData content
149153
| null content = Left $ ReadError "Empty file content"
150154
| otherwise = Right $ lines content
151-
152155
```
153156

154157
Define a function to transform the read data. It returns a `Right` with transformed data or a `Left` with a `TransformError`.
@@ -157,30 +160,29 @@ Define a function to transform the read data. It returns a `Right` with transfor
157160
transformData :: [String] -> Either TransformError [String]
158161
transformData lines
159162
| null lines = Left $ TransformError "No lines to transform"
160-
-- Simple transformation, where, we reverse each line.
163+
-- Simple transformation where we reverse each line.
161164
| otherwise = Right $ map reverse lines
162165
```
163166

164-
The outer `do` block, is using the `IO` monad, while the inner `do` block is using the `Either` monad. This code looks very much like imperative code, using the power of monad to allow for sequencing of operations. However, this is powerful, as it will allow the `Left` error to be threaded through the monadic `do` block, with the user not needing to handle the threading of the error state.
167+
The outer `do` block is using the `IO` monad, while the inner `do` block is using the `Either` monad. This code looks very much like imperative code, using the power of monads to allow for sequencing of operations. However, this is powerful, as it will allow the `Left` error to be threaded through the monadic `do` block, with the user not needing to handle the threading of the error state.
165168

166169
```haskell
167170
main :: IO ()
168171
main = do
169172
-- Attempt to read the file
170173
fileResult <- readFileSafe "example.txt"
171-
172174
let result = do
173175
-- Use monad instance to compute sequential operations
174176
content <- fileResult
175177
readData <- readData content
176-
transformData readdData
178+
transformData readData
177179
print result
178180
```
179181

180182
## Glossary
181183

182-
*Functor*: A type class in Haskell that represents types that can be mapped over. Instances of Functor must define the fmap function, which applies a function to every element in a structure.
184+
*Functor*: A type class in Haskell that represents types that can be mapped over. Instances of Functor must define the `fmap` function, which applies a function to every element in a structure.
183185

184-
*Applicative*: A type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions `pure` and (`<*>`).
186+
*Applicative*: A type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions `pure` and `(<*>)`.
185187

186-
*Monad*: A type class in Haskell that represents computations as a series of steps. It provides the bind operation (`>>=`) to chain operations and the return (or `pure`) function to inject values into the monadic context.
188+
*Monad*: A type class in Haskell that represents computations as a series of steps. It provides the bind operation `(>>=)` to chain operations and the `return` (or `pure`) function to inject values into the monadic context.

_chapters/frpanimated.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ title: "Functional Reactive Programming"
66

77
## Introduction
88

9-
This page will support the workshop solutions with a worked example of how we can use the observables, filled with pretty animations
9+
This page will support the workshop solutions with a worked example of how we can use the observables, filled with pretty animations.
1010

1111
## Animation Generation
1212

13-
Consider, the definitions for ranks, suits and card, as per the workshop:
13+
Consider the definitions for ranks, suits and cards, as per the workshop:
1414

1515
```typescript
16-
1716
const ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] as const;
1817
const suits = ['','','',''] as const;
1918

@@ -30,10 +29,10 @@ const suits$ = from(suits)
3029
const ranks$ = from(ranks)
3130
```
3231

33-
Using the webtool, we can visualize each of these streams.
32+
Using the webtool, we can visualise each of these streams.
3433

35-
![Rank Observable Visualized](/assets/images/chapterImages/frpanimated/rank.gif)
36-
![Suit Observable Visualized](/assets/images/chapterImages/frpanimated/suit.gif)
34+
![Rank Observable Visualised](/assets/images/chapterImages/frpanimated/rank.gif)
35+
![Suit Observable Visualised](/assets/images/chapterImages/frpanimated/suit.gif)
3736

3837
To create a card, for each suit, we can look through each rank, and create a string of suits and rank.
3938

@@ -43,7 +42,7 @@ const deck = suits$.pipe(
4342
map(rank => (`${suit}${rank}`)))))
4443
```
4544

46-
![Deck Observable Visualized](/assets/images/chapterImages/frpanimated/mapDeck.gif)
45+
![Deck Observable Visualised](/assets/images/chapterImages/frpanimated/mapDeck.gif)
4746

4847
However, this exists as four nested streams rather than one continuous flat stream. How do we fix this? We use mergeMap to merge the sub-streams into a single long continuous stream of cards. We now have a lovely little deck of cards :)
4948

@@ -53,23 +52,23 @@ const deck = suits$.pipe(
5352
map(rank => (`${suit}${rank}`)))))
5453
```
5554

56-
![Deck Observable Visualized](/assets/images/chapterImages/frpanimated/mergeMapDeck.gif)
55+
![Deck Observable Visualised](/assets/images/chapterImages/frpanimated/mergeMapDeck.gif)
5756

5857
However, this is only one deck? How can we create multiple decks. We will create a range, which will create a fixed range of numbers, and for each of those we can create a deck.
5958

6059
```typescript
6160
const decks = (numDecks : number) => range(0, numDecks).pipe(map(_ => deck))
6261
```
6362

64-
![Deck Observable Visualized](/assets/images/chapterImages/frpanimated/mapDecks.gif)
63+
![Deck Observable Visualised](/assets/images/chapterImages/frpanimated/mapDecks.gif)
6564

6665
But this poses a similar problem to the above issue with nested streams. So again, we use the power of mergeMap to flatten these streams in to one!
6766

6867
```typescript
6968
const decks = (numDecks : number) => range(0, numDecks).pipe(mergeMap(_ => deck))
7069
```
7170

72-
![Deck Observable Visualized](/assets/images/chapterImages/frpanimated/mergemapDecks.gif)
71+
![Deck Observable Visualised](/assets/images/chapterImages/frpanimated/mergemapDecks.gif)
7372

7473
All in order, oh no, let us shuffle them. Assuming we have these functions, which can insert an element in to a random position in an array. We will use the reduce, and the randomInsertion to shuffle them.
7574

@@ -83,25 +82,25 @@ function randomInsert<T>(a:readonly T[],e:T): readonly T[] {
8382
(impureRandomNumberGenerator(a.length + 1))
8483
}
8584
const shoe = (numDecks : number) => range(0, numDecks).pipe(
86-
mergeMap(_ => deck),
85+
mergeMap(_ => deck),
8786
reduce(randomInsert, [])
8887
)
8988
```
9089

9190
This should be correct? Not quite, we `reduce` to a single value, an array. So, now our stream contains a **single** element, an array, Rather, then being a stream of elements. This array will be all of our cards, shuffled. You can see that as we hover over the element and it attempts to print the contents.
9291

93-
![Deck Observable Visualized](/assets/images/chapterImages/frpanimated/singleItem.gif)
92+
![Deck Observable Visualised](/assets/images/chapterImages/frpanimated/singleItem.gif)
9493

9594
We need to turn this back into a stream. How can we do that, with the power of mergeMap! This will take our list and convert it to a stream, and then flatten it, such that our final result is a long stream of *shuffled* cards.
9695

9796
```typescript
9897
const shuffledShoe = (numDecks : number) => range(0, numDecks).pipe(
99-
mergeMap(_ => deck),
98+
mergeMap(_ => deck),
10099
reduce(randomInsert, []),
101100
mergeMap(from)
102101
)
103102
```
104103

105104
Wow, now we have a beautiful, shiny, shuffled shoe of cards in an Observable!
106105

107-
![Deck Observable Visualized](/assets/images/chapterImages/frpanimated/final.gif)
106+
![Deck Observable Visualised](/assets/images/chapterImages/frpanimated/final.gif)

0 commit comments

Comments
 (0)