update
This commit is contained in:
68
fp/.obsidian/workspace.json
vendored
68
fp/.obsidian/workspace.json
vendored
@@ -13,8 +13,8 @@
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Language/Functions/Defining/Guard Clause.md",
|
||||
"mode": "source",
|
||||
"file": "Language/Data Structures/type.md",
|
||||
"mode": "preview",
|
||||
"source": true
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@
|
||||
"state": {
|
||||
"type": "backlink",
|
||||
"state": {
|
||||
"file": "Language/Functions/Defining/Guard Clause.md",
|
||||
"file": "Language/Data Structures/type.md",
|
||||
"collapseAll": false,
|
||||
"extraContext": false,
|
||||
"sortOrder": "alphabetical",
|
||||
@@ -102,7 +102,7 @@
|
||||
"state": {
|
||||
"type": "outgoing-link",
|
||||
"state": {
|
||||
"file": "Language/Functions/Defining/Guard Clause.md",
|
||||
"file": "Language/Data Structures/type.md",
|
||||
"linksCollapsed": false,
|
||||
"unlinkedCollapsed": true
|
||||
}
|
||||
@@ -125,7 +125,7 @@
|
||||
"state": {
|
||||
"type": "outline",
|
||||
"state": {
|
||||
"file": "Language/Functions/Defining/Guard Clause.md"
|
||||
"file": "Language/Data Structures/type.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,42 +150,42 @@
|
||||
},
|
||||
"active": "6465d16034124b8f",
|
||||
"lastOpenFiles": [
|
||||
"Language/Functions/Defining/Pattern Matching.md",
|
||||
"Language/Functions/Applying.md",
|
||||
"Language/Functions/Composition.md",
|
||||
"Language/Functions/Currying.md",
|
||||
"Language/Functions.md",
|
||||
"Terminology/Point-free.md",
|
||||
"Language/Infix Operators/Common Operators/Applying & Composing Functions.md",
|
||||
"Language/Functions/Defining.md",
|
||||
"Language/Expressions/case .. of.md",
|
||||
"Language/Expressions/Lambda Functions.md",
|
||||
"Language/Functions/Defining/Guard Clause.md",
|
||||
"Language/Expressions/let .. in ...md",
|
||||
"Language/Expressions/if .. then .. else ...md",
|
||||
"Language/Expressions/do notation.md",
|
||||
"Classes/Functor/Apply.md",
|
||||
"Classes/Functor/Applicative.md",
|
||||
"Classes/Bind/Monad.md",
|
||||
"Language/Data Structures/data.md",
|
||||
"Language/Data Structures/Records.md",
|
||||
"Language/Data Structures/newtype.md",
|
||||
"Monads/Identity.md",
|
||||
"Monads/Untitled",
|
||||
"Data/Void.md",
|
||||
"Data/Unit.md",
|
||||
"Examples/Chess Engine/002 Moves.md",
|
||||
"Examples/Chess Engine/001 Types.md",
|
||||
"Examples/Chess Engine/000 The problem.md",
|
||||
"Data/Collections/Array.md",
|
||||
"Classes/Bind/Bind.md",
|
||||
"Classes/Alternative/Alt.md",
|
||||
"Classes/Alternative/Plus.md",
|
||||
"Monads/Transformers/MaybeT.md",
|
||||
"Monads/Transformers/ExceptT.md",
|
||||
"Monads/Transformers/ReaderT.md",
|
||||
"Monads/Transformers/WriterT.md",
|
||||
"Monads/Transformers/StateT.md",
|
||||
"Monads/Transformers",
|
||||
"Classes/Alternative/Alternative.md",
|
||||
"Classes/Alternative/Alt.md",
|
||||
"Classes/Bind/Monad.md",
|
||||
"Classes/Enum/Bounded.md",
|
||||
"Classes/Functor/Applicative.md",
|
||||
"Classes/Functor/Functor.md",
|
||||
"Classes/Functor/Apply.md",
|
||||
"Classes/Math/DivisionRing.md",
|
||||
"Classes/Foldable.md",
|
||||
"Classes/Hashable.md",
|
||||
"Classes/Enum/Enum.md",
|
||||
"Classes/Enum/BoundedEnum.md",
|
||||
"Classes/Enum",
|
||||
"Language/Generics",
|
||||
"Language/Functions/Defining/Guard Clause.md",
|
||||
"Examples/Chess Engine",
|
||||
"Examples",
|
||||
"Language/Functions/Defining/Pattern Matching.md",
|
||||
"Monads/Transformers",
|
||||
"Classes/Collapsing",
|
||||
"Classes/Bind",
|
||||
"Classes/Functor",
|
||||
"Classes/Alternative",
|
||||
"Data/Product",
|
||||
"Data/Sum",
|
||||
"Data/Collections",
|
||||
"Language/Row Types",
|
||||
"Classes/Math",
|
||||
"Untitled 1.canvas",
|
||||
"Untitled.canvas"
|
||||
]
|
||||
|
||||
0
fp/Classes/Enum/Bounded.md
Normal file
0
fp/Classes/Enum/Bounded.md
Normal file
0
fp/Classes/Enum/BoundedEnum.md
Normal file
0
fp/Classes/Enum/BoundedEnum.md
Normal file
0
fp/Classes/Enum/Enum.md
Normal file
0
fp/Classes/Enum/Enum.md
Normal file
0
fp/Classes/Hashable.md
Normal file
0
fp/Classes/Hashable.md
Normal file
@@ -1,2 +0,0 @@
|
||||
# What
|
||||
# Why
|
||||
0
fp/Data/Unit.md
Normal file
0
fp/Data/Unit.md
Normal file
0
fp/Data/Void.md
Normal file
0
fp/Data/Void.md
Normal file
1
fp/Examples/Chess Engine/000 The problem.md
Normal file
1
fp/Examples/Chess Engine/000 The problem.md
Normal file
@@ -0,0 +1 @@
|
||||
In this example we'll write a framework for writing chess engines
|
||||
95
fp/Examples/Chess Engine/001 Types.md
Normal file
95
fp/Examples/Chess Engine/001 Types.md
Normal file
@@ -0,0 +1,95 @@
|
||||
Type-driven development is a methodology of modeling domain-specific data as types, for example a chess engine framework will need moves, sides (white/black), pieces, the board and piece coordinates.
|
||||
|
||||
By thinking about how we'll model this for a bit and starting with this step, we'll be able to have powerful but simple abstractions "fall out" of the design later that will make our life very easy.
|
||||
|
||||
Here's how I might start out:
|
||||
|
||||
```haskell
|
||||
data Side = White | Black
|
||||
derive instance Generic Side _
|
||||
derive instance Eq Side
|
||||
|
||||
data Piece
|
||||
= Pawn
|
||||
| Knight
|
||||
| Bishop
|
||||
| Rook
|
||||
| Queen
|
||||
| King
|
||||
derive instance Generic Side _
|
||||
derive instance Eq Side
|
||||
|
||||
data File = A | B | C | D | E | F | G
|
||||
derive instance Generic File _
|
||||
derive instance Eq File
|
||||
derive instance Ord File
|
||||
instance Enum File where -- <snip>
|
||||
instance Bounded File where -- <snip>
|
||||
instance BoundedEnum File where -- <snip>
|
||||
|
||||
data Rank = R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8
|
||||
derive instance Generic Rank _
|
||||
derive instance Eq Rank
|
||||
derive instance Ord Rank
|
||||
instance Enum Rank where -- <snip>
|
||||
instance Bounded Rank where -- <snip>
|
||||
instance BoundedEnum Rank where -- <snip>
|
||||
|
||||
data Pos = Pos File Rank
|
||||
derive instance Generic Pos _
|
||||
derive instance Eq Pos
|
||||
derive instance Ord Pos
|
||||
|
||||
data MoveKind = To | Promotes Piece
|
||||
derive instance Generic MoveKind _
|
||||
derive instance Eq MoveKind
|
||||
|
||||
data Move = Move Pos MoveKind Pos
|
||||
derive instance Generic Move _
|
||||
derive instance Eq Move
|
||||
|
||||
data Board = Board (Map Pos (Side /\ Piece))
|
||||
derive instance Generic Board _
|
||||
derive instance Eq Board
|
||||
|
||||
data Game = Game (NonEmptyArray Board)
|
||||
derive instance Generic Game _
|
||||
```
|
||||
|
||||
The starting board would look something like:
|
||||
|
||||
```haskell
|
||||
boardInit :: Board
|
||||
boardInit =
|
||||
let
|
||||
pieces rank side =
|
||||
[ Pos A rank /\ side /\ Rook
|
||||
, Pos B rank /\ side /\ Knight
|
||||
, Pos C rank /\ side /\ Bishop
|
||||
, Pos D rank /\ side /\ Queen
|
||||
, Pos E rank /\ side /\ King
|
||||
, Pos F rank /\ side /\ Bishop
|
||||
, Pos G rank /\ side /\ Knight
|
||||
, Pos H rank /\ side /\ Rook
|
||||
]
|
||||
pawns rank side =
|
||||
(upFromIncluding bottom :: Array File)
|
||||
<#> \file -> Pos file rank /\ side /\ Pawn
|
||||
in
|
||||
Board
|
||||
$ Map.fromFoldable
|
||||
$ pieces R1 White
|
||||
<> pawns R2 White
|
||||
<> pawns R7 Black
|
||||
<> pieces R8 Black
|
||||
|
||||
gameInit :: Game
|
||||
gameInit = Game $ pure boardInit
|
||||
```
|
||||
|
||||
> [!tip]- Performance Optimization
|
||||
> [[Map]] is a B-tree map, meaning lookups use bifurcation in `O(log(n))` time. To do position lookups in constant time, we could replace Map with [[HashMap]], and add [[Hashable]] implementations to `Pos`.
|
||||
|
||||
> [!tip]- Enums for rank & file
|
||||
> A temptation may be to use [[Int]] for encoding rank & file in positions, but this could quickly get hairy with legal bounds-checking.
|
||||
> By encoding X & Y as enums `Rank` & `File`, we've made it impossible to put illegal positions into the `Board`.
|
||||
69
fp/Examples/Chess Engine/002 Moves.md
Normal file
69
fp/Examples/Chess Engine/002 Moves.md
Normal file
@@ -0,0 +1,69 @@
|
||||
Now that we have the data modeled, we can start with the bottom-most abstractions and build from there.
|
||||
|
||||
Some questions we'll need to answer:
|
||||
- what legal moves does a piece have?
|
||||
- what is the material balance of the game?
|
||||
- is there a check or mate in this position?
|
||||
|
||||
In order to move, though, we'll need to be able to add integers to ranks and files:
|
||||
|
||||
> [!tip]- toEnum
|
||||
> [[BoundedEnum]] provides `toEnum`, which happens to be a really quick way of solving the "into & out of" integers for these 2 datatypes.
|
||||
> One difference is that [[BoundedEnum]]'s int representation is 0-based, but I think it makes more sense to have these be 1-based, so we subtract 1 before calling `toEnum` and add 1 after `fromEnum`.
|
||||
|
||||
```haskell
|
||||
rankFromInt :: Int -> Maybe Rank
|
||||
rankFromInt = toEnum <<< (_ - 1)
|
||||
|
||||
fileFromInt :: Int -> Maybe File
|
||||
fileFromInt = toEnum <<< (_ - 1)
|
||||
|
||||
rankToInt :: Rank -> Int
|
||||
rankToInt = (_ + 1) <<< fromEnum
|
||||
|
||||
fileToInt :: File -> Int
|
||||
fileToInt = (_ + 1) <<< fromEnum
|
||||
|
||||
addRank :: Rank -> Int -> Maybe Rank
|
||||
addRank a by = rankFromInt $ rankToInt a + by
|
||||
|
||||
addFile :: File -> Int -> Maybe File
|
||||
addFile a by = fileFromInt $ fileToInt a + by
|
||||
```
|
||||
|
||||
Now we can write a simple low-level `possibleMoves` function that yields all moves for a piece that keeps it within the bounds of the board (we'll check later if that is illegal for other reasons)
|
||||
```haskell
|
||||
possibleMoves :: Side -> Piece -> Pos -> Array Pos
|
||||
possibleMoves Black Pawn (Pos r f)
|
||||
| r == R7 = [Pos R6 f, Pos R5 f]
|
||||
| otherwise =
|
||||
maybe [] pure
|
||||
$ (flip Pos f) <$> addRank r (-1)
|
||||
possibleMoves White Pawn (Pos r f)
|
||||
| r == R2 = [Pos R3 f, Pos R4 f]
|
||||
| otherwise =
|
||||
maybe [] pure
|
||||
$ (flip Pos f) <$> addRank r 1
|
||||
possibleMoves _ Knight (Pos r f) =
|
||||
Array.catMaybes
|
||||
[ Just Pos <*> addRank (-1) r <*> addFile (-2) f
|
||||
, Just Pos <*> addRank (-2) r <*> addFile (-1) f
|
||||
, Just Pos <*> addRank (1) r <*> addFile (-2) f
|
||||
, Just Pos <*> addRank (2) r <*> addFile (-1) f
|
||||
, Just Pos <*> addRank (-1) r <*> addFile (2) f
|
||||
, Just Pos <*> addRank (-2) r <*> addFile (1) f
|
||||
, Just Pos <*> addRank (1) r <*> addFile (2) f
|
||||
, Just Pos <*> addRank (2) r <*> addFile (1) f
|
||||
]
|
||||
possibleMoves _ King (Pos r f) =
|
||||
Array.catMaybes
|
||||
[ Just Pos <*> addRank (-1) r <*> addFile (-1) f
|
||||
, Just Pos <*> addRank 1 r <*> addFile (-1) f
|
||||
, Just Pos <*> addRank (-1) r <*> addFile 1 f
|
||||
, Just Pos <*> addRank 1 r <*> addFile 1 f
|
||||
, Just Pos <*> addRank 0 r <*> addFile 1 f
|
||||
, Just Pos <*> addRank 0 r <*> addFile (-1) f
|
||||
, Just Pos <*> addRank 1 r <*> addFile 0 f
|
||||
, Just Pos <*> addRank (-1) r <*> addFile 0 f
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,97 @@
|
||||
`data` is how data structures are defined.
|
||||
|
||||
Data structures have a type name and any number of constructors, special functions that store data.
|
||||
|
||||
## Syntax
|
||||
```haskell
|
||||
data <type_name> <type_parameters>
|
||||
= <constructor>
|
||||
| ...
|
||||
```
|
||||
|
||||
### type_name
|
||||
a PascalCase identifier that is the type of the data structure
|
||||
|
||||
### type_parameters
|
||||
space-separated list of 0 or more generic type variables
|
||||
|
||||
### constructor
|
||||
a PascalCase identifier with 0 or more arguments. This identifier is used as a function that constructs data of type `type_name`.
|
||||
|
||||
## Data Structure Types
|
||||
### Newtype
|
||||
A newtype is a name for a data structure with one constructor accepting one argument.
|
||||
|
||||
Newtypes are used to add or customize behavior for types which can't otherwise be extended.
|
||||
|
||||
```haskell
|
||||
data Identity a = Identity a
|
||||
```
|
||||
|
||||
[[Identity]] is a very common newtype, allowing the use of [[do notation]] without using an actual monad that needs to be evaluated like [[Effect]] or [[Maybe]].
|
||||
|
||||
This pattern is so common and useful that there is a special subset of `data` structures specifically for writing newtypes; the [[newtype]] keyword.
|
||||
|
||||
### Sum Type
|
||||
A [sum type](https://en.wikipedia.org/wiki/Tagged_union) is a data structure that may be in one of multiple possible states.
|
||||
|
||||
When you have an instance of a sum type and you want to handle a particular case, you need to exhaustively handle all cases.
|
||||
|
||||
```haskell
|
||||
data Maybe a = Just a | Nothing
|
||||
```
|
||||
|
||||
[[Maybe]] and [[Either]] are common examples of sum types, and their "or-ness" is what gives them their meaning.
|
||||
|
||||
### Product Type
|
||||
While sum types encode "A or B", product types encode "A and B."
|
||||
|
||||
```haskell
|
||||
data Tuple a b = Tuple a b
|
||||
```
|
||||
|
||||
[[Tuple|Tuples]], for example, are encoded this way. In order to construct a tuple, you need to provide both fields.
|
||||
|
||||
## Examples
|
||||
|
||||
#### No constructors
|
||||
```haskell
|
||||
data Void
|
||||
```
|
||||
[[Void]] has no constructors, so it can **never** be created.
|
||||
|
||||
#### One constructor
|
||||
```haskell
|
||||
data IntNonZero = INZ Int
|
||||
```
|
||||
|
||||
> [!hint]- Constructor Naming
|
||||
> In this example I've named the type `IntNonZero` and its constructor `INZ` differently to show that constructors do not have to have the same name as the type they construct.
|
||||
>
|
||||
> If this were an actual type, I would probably name the constructor `IntNonZero` instead of `INZ`, though.
|
||||
|
||||
Here, we create a type `IntNonZero` that can be created by calling `INZ :: Int -> IntNonZero` with an [[Int]].
|
||||
|
||||
#### Multiple constructors
|
||||
```haskell
|
||||
data AnimalType = Cow | Dog | Cat
|
||||
```
|
||||
|
||||
`AnimalType` has 3 possible constructors: `Cow`, `Dog` and `Cat`.
|
||||
|
||||
#### Parameterized
|
||||
```haskell
|
||||
data Identity a = Identity a
|
||||
```
|
||||
|
||||
Declare a type [[Identity]], generic over some other type `a`.
|
||||
|
||||
You can create one with the constructor `Identity`, e.g.
|
||||
|
||||
```haskell
|
||||
Identity 1
|
||||
Identity 1.0
|
||||
Identity ""
|
||||
Identity []
|
||||
Identity unit
|
||||
```
|
||||
|
||||
0
fp/Language/Data Structures/newtype.md
Normal file
0
fp/Language/Data Structures/newtype.md
Normal file
0
fp/Monads/Identity.md
Normal file
0
fp/Monads/Identity.md
Normal file
Reference in New Issue
Block a user