Skip to content

Commit ca8e191

Browse files
committed
case study
1 parent 434d4ac commit ca8e191

32 files changed

+912
-197
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
.vscode/
2-
dist-newstyle
2+
dist-newstyle/*

README.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# **Under :construction: !!**
2-
31
# Introduction to Haskell
42

53
This is the github repository for [an introductory guide to learning Haskell](https://haskell-docs.netlify.app/).
@@ -25,8 +23,8 @@ With that in mind, it should be:
2523

2624
| | Wiki | LYAH | RWH | LHBG | These Docs | Various Books |
2725
| ------------------ | ------------------ | ----------------- | ------------------ | ------------------ | ------------------ | ------------------ |
28-
| Free | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
2926
| Community editable | :white_check_mark: | :x: | :x: | :pushpin: | :white_check_mark: | :x: |
27+
| Free | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
3028
| Beginner friendy | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
3129
| Up-to-date | :x: | :x: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
3230
| Looks nice | :x: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: |

dist-newstyle/cache/compiler

212 Bytes
Binary file not shown.

dist-newstyle/cache/config

24 Bytes
Binary file not shown.

dist-newstyle/cache/elaborated-plan

382 Bytes
Binary file not shown.

dist-newstyle/cache/improved-plan

528 Bytes
Binary file not shown.

dist-newstyle/cache/plan.json

+1-1
Large diffs are not rendered by default.

dist-newstyle/cache/solver-plan

42 Bytes
Binary file not shown.

dist-newstyle/cache/source-hashes

-2 Bytes
Binary file not shown.

dist-newstyle/cache/up-to-date

-2 Bytes
Binary file not shown.

docs/docs/basics/syntax.md

+16-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,22 @@ example input = result <> " tree" where
1515

1616
## Infixing and sections
1717

18-
For operators like `+` or `/` that are written *infix* (i.e. in between their two arguments), as opposed to *prefix* (before their arguments), Haskell has some syntactic sugar:
18+
Given a function `f`, one can write it *infix* (i.e. in between its two arguments):
19+
20+
```hs title="repl example"
21+
-- treating a prefix function as an infix function
22+
> let subtract x y = x - y
23+
> subtract 6 4
24+
2
25+
> 6 `subtract` 4
26+
2
27+
```
28+
29+
Functions whose names are symbols, like `+`, `$` and `.`, are written infix by default. An order of precedence is defined, to avoid the need for bracketing. For example, `f a . f b` means `(f a) . (f b)`, and similarly, `f a $ f b` means `(f a) $ (f b)`.
30+
31+
32+
33+
For functions like `+` or `/` that are written by default *infix*, Haskell has some syntactic sugar to convert functions from infix to *prefix* (before their arguments):
1934

2035
```haskell title="repl example"
2136

@@ -30,13 +45,6 @@ For operators like `+` or `/` that are written *infix* (i.e. in between their tw
3045
0.4
3146
> (5 /) 2
3247
2.5
33-
34-
-- treating a prefix function as an infix function
35-
> let subtract x y = x - y
36-
> subtract 6 4
37-
2
38-
> 6 `subtract` 4
39-
2
4048
```
4149

4250
1. Whether infix or not, the type of `(/)` is `#!haskell Double -> (Double -> Double)`.

docs/docs/basics/types.md

-7
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,6 @@ exampleText = "hello world!"
7979
1. See [here](/gotchas/strings) for why this extension is needed.
8080

8181

82-
83-
84-
<!-- Haskell's type system is such an important feature, and so useful for understanding the language, that it is a good place to begin.
85-
86-
Every expression (that includes all programs) in the language has a unique type. -->
87-
88-
8982
## The type of functions
9083

9184
A function in Haskell means the same as a function in mathematics: it takes an input and produces an output. The type of the function depends on the type of the input and the type of the output.

docs/docs/casestudy/chess.md

+81-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# Under :construction:
1+
---
2+
comments: true
3+
---
24

35

46
```hs title="Chess.hs" linenums="1"
@@ -7,13 +9,87 @@
79

810
1. A [sum](/basics/createdata/#sums) type.
911
2. A [product](/basics/createdata/#products) type.
10-
3.
11-
4.
12+
3. Module should have same name as file.
13+
4. Automatically [derive](/typeclasses/overview/#automatically-deriving-instances) [Eq](/typeclasses/survey/#eq) and [Show](/typeclasses/survey/#show) [typeclasses](/typeclasses/overview)
14+
5. `Enum` allows for writing e.g. `[A .. H]`
15+
6. Alternative approaches [include](/casestudy/chess/#initboard)
16+
7. Another syntax for custom datatypes, know as [GADT](/basics/createdata/#products)
17+
8. A list comprehension, as in Python.
18+
9. `into` is from the [witch](/faqs/convertingnumbers) package, for type coercions. `@Text` indicates the type to coerce to.
19+
10. No need to write type signatures (although it's good practice) - Haskell will infer them for you.
20+
11. `\case` requires the `LambdaCase` extension which has been globally enabled in the project's `.cabal` file.
21+
12. If `Black`, return function to uppercase the character.
22+
13. If `White`, don't change the letter case. `id` can be useful.
23+
14. `newtype` is like the `data` keyword. See more about `newtype` [here](https://kowainik.github.io/posts/haskell-mini-patterns)
24+
15. Alternative approaches to the `Board` type [include](/casestudy/chess/#board).
1225

13-
## Comments
26+
## Analysis
27+
28+
Because custom types are so easily made and expressive, it is typical in Haskell to create types that model your problem domain (here various chess-related types).
29+
30+
The central type is `Board`, which represents the state of a chessboard. We have chosen to directly represent the board's state as a function from a square (specified by file and rank) to the square's state.
31+
32+
A good example of [type-based refactoring](/thinkingfunctionally/typeinference/#type-based-refactoring) in Haskell is to change `Board` to an [alternative representation](/casestudy/chess/#board) and then fix the series of type errors that Haskell will show you in order to make the new `Board` type work across your project.
1433

1534
## Alternative approaches
1635

36+
### `initBoard`
37+
38+
=== "original"
39+
40+
```hs
41+
initBoard :: Board
42+
initBoard = Board $ \f r -> Empty
43+
```
44+
45+
=== "wildcards"
46+
47+
```hs
48+
initBoard :: Board
49+
initBoard = Board $ \_ _ -> Empty
50+
```
51+
52+
=== "with `curry` and `const`"
53+
54+
```hs
55+
initBoard :: Board
56+
initBoard = Board $ curry $ const Empty
57+
```
58+
59+
!!! Tip
60+
To understand how this works, lookup the types of `const` and `curry` on [Hoogle](/gettingstarted/overview/#step-4-hoogle) (both are common and useful functions).
61+
62+
Then ascertain the type of `const Empty` with VSCode, namely:
63+
64+
- `#!hs (const Empty) :: (File, Rank) -> SquareState`
65+
66+
Convince yourself that this is an appropriate input type for `curry`, and an appropriate output type for `const`.
67+
68+
### `Board`
69+
70+
=== "original"
71+
72+
```hs
73+
data SquareState where
74+
Empty :: SquareState
75+
HasPiece :: Piece -> SquareState
76+
77+
newtype Board where
78+
Board :: (File -> Rank -> SquareState) -> Board
79+
```
80+
81+
=== "with dictionary"
82+
83+
```hs
84+
85+
import qualified Data.Map as M
86+
type Board = M.Map (File, Rank) Piece
87+
88+
```
89+
90+
91+
<!-- ## Alternative approaches
92+
1793
=== "original"
1894
1995
```hs
@@ -65,5 +141,5 @@
65141
where
66142
67143
inRange = (`elem` [1..8])
68-
```
144+
``` -->
69145

docs/docs/casestudy/evaluator.md

+85-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,88 @@
1-
# Under :construction:
1+
---
2+
comments: true
3+
---
4+
5+
6+
27

38
```hs title="Evaluator.hs" linenums="1"
49
--8<-- "../src/Evaluator.hs"
5-
```
10+
```
11+
12+
1. Throw an error of type `ChessError`. This is what requires the `MonadError ChessError` constraint.
13+
2. `gets display` is the same as `fmap display get`: it accesses the state (of type `Board`), which requires the `MonadState Board` constraint, and applies `display` to it, to return a `Text` value.
14+
3. `get` is the local state. It takes no arguments.
15+
4. Recall that a `Board` value represents the board as a function from a `File` and `Rank` to a square state, so this function is what we need to change, when updating the `Board`.
16+
5. `put` takes an argument and sets the state to that argument.
17+
18+
## Analysis
19+
20+
`MonadError` and `MonadState` are [typeclasses](/typeclasses/overview) for types with the ability to throw errors and mutate local state respectively. See the [monad transformer library (mtl)](todo) for more.
21+
22+
With that in mind, read the type signature of `evaluate` as follows: `evaluate` is a function that takes an `Instruction` and returns `Text`, but with the possibility of throwing an error of type `ChessError`, and of changing the state (of type `Board`).
23+
24+
We can think of `evaluate` as taking a synactic description of an `Instruction` and evaluating it into a result. For example `#!hs ReplInstruction "quit"` is a description of an instruction, but `#!hs throwError Exit` is the actually "program" that will quit the repl.
25+
26+
## `evaluate`
27+
28+
=== "original"
29+
30+
```hs
31+
evaluate :: (MonadError ChessError m, MonadState Board m) =>
32+
Instruction -> m Text
33+
evaluate instr = case instr of
34+
ReplInstruction "quit" -> throwError Exit
35+
ReplInstruction "display" -> gets display
36+
Set file rank piece -> do
37+
(Board boardFunc) <- get
38+
let newBoard =
39+
Board
40+
( \f r ->
41+
if f == file && r == rank
42+
then HasPiece piece
43+
else boardFunc f r
44+
)
45+
put newBoard
46+
return $ display newBoard
47+
ReplInstruction _ -> throwError $ ReplError "no such instruction"
48+
```
49+
50+
=== "with `\case`"
51+
52+
```hs hl_lines="3"
53+
evaluate' :: (MonadError ChessError m, MonadState Board m) =>
54+
Instruction -> m Text
55+
evaluate' = \case
56+
ReplInstruction "quit" -> throwError Exit
57+
ReplInstruction "display" -> gets display
58+
Set file rank piece -> do
59+
(Board boardFunc) <- get
60+
let newBoard =
61+
Board
62+
( \f r ->
63+
if f == file && r == rank
64+
then HasPiece piece
65+
else boardFunc f r
66+
)
67+
put newBoard
68+
return $ display newBoard
69+
ReplInstruction _ -> throwError $ ReplError "no such instruction"
70+
```
71+
72+
=== "with `modify` and `gets`"
73+
74+
```hs
75+
evaluate :: (MonadError ChessError m, MonadState Board m) =>
76+
Instruction -> m Text
77+
evaluate instr = case instr of
78+
ReplInstruction "quit" -> throwError Exit
79+
ReplInstruction "display" -> gets display
80+
Set file rank piece -> do
81+
let updateBoard (Board boardFunc) = Board ( \f r ->
82+
if f == file && r == rank
83+
then HasPiece piece
84+
else boardFunc f r)
85+
modify updateBoard
86+
gets display
87+
ReplInstruction _ -> throwError $ ReplError "no such instruction"
88+
```

docs/docs/casestudy/json.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
---
2+
comments: true
3+
---
4+
15
# Under :construction:
26

37
```hs title="JSON.hs" linenums="1"

docs/docs/casestudy/overview.md

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,55 @@
1-
# Under :construction:
1+
---
2+
comments: true
3+
---
24

35
This section is a walkthrough of a [project](https://github.com/reubenharry/haskell-docs) with the [code](https://github.com/reubenharry/haskell-docs/tree/main/src) intended to display how to write a simple but real-world application in Haskell.
46

57
The subsections are ordered by difficulty.
68

9+
!!! Tip
10+
We strongly encourage reading Haskell code in a text editor with the [IDE](/gettingstarted/overview/#step-3-set-up-the-haskell-language-server) activated: mousing over variables will show their type and where they are defined, which is especially helpful when learning. ++command++ + `click` to jump to a definition.
11+
12+
## Demo:
13+
14+
```sh
15+
cabal run chess-repl
16+
17+
Welcome!
18+
19+
20+
>
21+
place a white bishop on a5
22+
_|_|_|_|b|_|_|_
23+
_|_|_|_|_|_|_|_
24+
_|_|_|_|_|_|_|_
25+
_|_|_|_|_|_|_|_
26+
_|_|_|_|_|_|_|_
27+
_|_|_|_|_|_|_|_
28+
_|_|_|_|_|_|_|_
29+
_|_|_|_|_|_|_|_
30+
>
31+
place a black castle on b6
32+
1:15:
33+
|
34+
1 | place a black castle on b6
35+
| ^^^^^^
36+
unexpected "castle"
37+
expecting "bishop", "king", "knight", "pawn", "queen", "rook", or white space
38+
39+
>
40+
:d
41+
_|_|_|_|b|_|_|_
42+
_|_|_|_|_|_|_|_
43+
_|_|_|_|_|_|_|_
44+
_|_|_|_|_|_|_|_
45+
_|_|_|_|_|_|_|_
46+
_|_|_|_|_|_|_|_
47+
_|_|_|_|_|_|_|_
48+
_|_|_|_|_|_|_|_
49+
>
50+
:q
51+
```
52+
753
!!! Tip
854
If you encounter an expression in this codebase that you don't understand, use Haskell's purity and immutability to your advantage: you can't always break it down into its parts and understand them separately.
955

0 commit comments

Comments
 (0)