website/src/content/slides/dead-simple-haskell/index.md

1200 lines
18 KiB
Markdown
Raw Normal View History

2024-02-04 01:00:25 +01:00
---
title: Dead simple Haskell
date: 2024-01-11T21:21:41.263Z
slug: dead-simple-haskell
animate: true
---
# Dead simple Haskell
![Haskell logo](/static/content/slides/haskell-molecules/haskell.png)
-----
## Haskell molecules
-----
### Atoms
![atoms](/static/content/slides/haskell-molecules/atoms.png)
---
![atoms](/static/content/slides/haskell-molecules/atoms.png)
```haskell
data H = H -- hydrogen
data O = O -- oxygen
data C = C -- carbon
```
-----
### Molecules
![Atoms](/static/content/slides/haskell-molecules/molecules.png)
---
![Atoms](/static/content/slides/haskell-molecules/molecules.png)
```haskell
type H₂O = (H, O, H) -- water
type O₂ = (O, O) -- oxygen (gas)
type CO₂ = (O, C, O) -- carbon dioxide
```
-----
### Reactions
![Magic](/static/content/slides/haskell-molecules/magic.png)
---
![More magic](/static/content/slides/haskell-molecules/more-magic.png)
---
![More magic](/static/content/slides/haskell-molecules/more-magic.png)
```haskell ignore
makeWater :: H -> H -> O -> H₂O
```
```haskell ignore
makeOxygen :: O -> O -> O₂
```
```haskell ignore
burnOxygen :: C -> O₂ -> CO₂
```
---
```haskell
makeWater :: H -> H -> O -> H₂O
```
```haskell
makeWater h1 h2 o = (h1, o, h2)
```
~
```haskell
makeOxygen :: O -> O -> O₂
```
```haskell
makeOxygen o1 o2 = (o1, o2)
```
~
```haskell
burnOxygen :: C -> O₂ -> CO₂
```
```haskell
burnOxygen c (o1, o2) = (o1, c, o2)
```
-----
### `.` `$`
![Plumbing](/static/content/slides/haskell-molecules/plumbing.png)
---
![Partial application](/static/content/slides/haskell-molecules/partial.png)
```haskell ignore
λ> :type makeOxygen
makeOxygen :: O -> O -> O₂
```
```haskell ignore
λ> :type makeOxygen O
makeOxygen O :: O -> O₂
```
```haskell ignore
λ> :type makeOxygen O O
makeOxygen O O :: O₂
```
---
![Combustion](/static/content/slides/haskell-molecules/combustion.png)
```haskell ignore
λ> :type burnOxygen
burnOxygen :: C -> O₂ -> CO₂
```
```haskell ignore
λ> :type burnOxygen C
burnOxygen C :: O₂ -> CO₂
```
```haskell ignore
λ> :type burnOxygen C (O, O)
burnOxygen C O₂ :: CO₂
```
---
![Two functions](/static/content/slides/haskell-molecules/two-fs.png)
```haskell
f1 :: O -> O₂
f1 = makeOxygen O
f2 :: O₂ -> CO₂
f2 = burnOxygen C
```
---
![Composition](/static/content/slides/haskell-molecules/composition.png)
```haskell
f3 = f2 . f1
```
---
![Composition2](/static/content/slides/haskell-molecules/composition2.png)
```haskell
f3 :: O -> CO₂
```
```haskell ignore
f1 :: { O -> O₂ }
f2 :: [ O₂ -> CO₂ ]
f3 :: {O -> [ O₂ } -> CO₂ ]
f3 :: O -> CO₂
```
---
```haskell ignore
f3' o = f2 . f1 o
```
```scala
Diagnostics:
1. • Couldn't match type: (O, O)
with: a -> O₂
Expected: a -> O₂
Actual: O₂
• Possible cause: f1 is applied to too many arguments
In the second argument of (.), namely f1 o
In the expression: f2 . f1 o
In an equation for f3': f3' o = f2 . f1 o
• Relevant bindings include
f3' :: O -> a -> CO₂
(bound at Script.hs:112:1) [-Wdeferred-type-errors]
```
---
![Composition3](/static/content/slides/haskell-molecules/composition3.png)
```haskell ignore
f3' o = f2 . f1 o
```
---
![Funnel](/static/content/slides/haskell-molecules/funnel.png)
```haskell ignore
f3' o = f2 . f1 $ o
-- OR
f3' o = f2 $ f1 o
```
---
```haskell ignore
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)
infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x
```
-----
## Isotopes
![Heavy water](/static/content/slides/haskell-molecules/heavy-water.gif)
---
![Isotopes](/static/content/slides/haskell-molecules/isotopes.png)
---
```haskell
data H'
= H¹-- protium
| H²-- deuterium
| H³-- tritium
type H2O' = (H', O, H') -- water
```
```haskell
makeWater' :: H' -> H' -> O -> H2O'
makeWater' h1 h2 o = (h1, o, h2)
```
---
### Algebra
How many values inhibit each of these?
Type | Possibilities
---------- | -------------
`()` | ?
`O` | ?
`H'` | ?
`(H', H')` | ?
---
`()`
---
`()`
1
`()`
---
`O`
---
`O`
1
`O`
---
`H'`
---
`H'`
3
`H¹` `H²` `H³`
---
`(H', H')`
---
`(H', H')`
9
`(H¹, H¹)` `(H¹, H²)` `(H¹, H³)`
`(H², H¹)` `(H², H²)` `(H², H³)`
`(H³, H¹)` `(H³, H²)` `(H³, H³)`
---
`(H', Bool)`
---
`(H', Bool)`
6
`(H¹, True)` `(H¹, False)`
`(H², True)` `(H², False)`
`(H³, True)` `(H³, False)`
---
Type | Inhibitants
---------- | -----------
`()` | 1
`Bool` | 2
`H'` | 3
`A` | a
`B` | b
`(A, B)` | a × b
`A` \| `B` | a + b
---
Product types Π
Type | Inhibitants
---------- | -----------
() | 1
Bool | 2
((), Bool) | 1 × 2 = 2
(H', Bool) | 3 × 2 = 6
```haskell ignore
data Pair a b = Pair a b
```
---
```haskell ignore
type Pair a b = (a, b)
```
```haskell ignore
data Pair a b = Pair a b
```
```haskell ignore
data Pair a b = Pair
{ fieldA :: a
, fieldB :: b
}
```
---
Sum types Σ
Type | Inhibitants
---------- | -----------
() | 1
Bool | 2
2024-02-04 15:34:58 +01:00
() \| Bool | 1 + 2 = 3
H' \| Bool | 3 + 2 = 5
2024-02-04 01:00:25 +01:00
```haskell ignore
data Either a b = Left a | Right b
```
---
Why not both?
---
Why not both?
```haskell ignore
data Crazy a b
= CrazyA a
| CrazyB b
| Both a b
| Neither
```
---
```haskell ignore
data Crazy a b
= CrazyA a
| CrazyB b
| Both a b
| Neither
```
Member | Inhibitants
---------- | -----------
`CrazyA a` | a
`CrazyB b` | b
`Both a b` | a × b
`Neither` | 1
Σ | a + b + a × b + 1
---
Can there be a "0"?
---
Can there be a "0"?
Yes
```haskell ignore
Crazy Void ()
= CrazyA Void
| CrazyB ()
| Both Void ()
| Neither
```
Σ = 0 + 1 + 0 × 1 + 1 = 2
-----
### Traits
![Neon colors](/static/content/slides/haskell-molecules/neon.png)
---
Element name | Color
------------ | ------
Helium | orange
Neon | red
Argon | lavender
Krypton | white
Xenon | blue
Radon | red
---
```haskell
data He = He -- Helium
data Ne = Ne -- Neon
data Ar = Ar -- Argon
data Kr = Kr -- Krypton
data Xe = Xe -- Xenon
data Rn = Rn -- Radon
```
---
How to convert noble gases to color?
```haskell ignore
toColor :: ? -> String
```
---
What about allowing everything in?
```haskell ignore
toColor :: a -> String
toColor a
| a == He = "orange"
| a == Ne = "red"
| otherwise = undefined
-- ???
_ = toColor "anything in"
_ = toColor 1234
```
---
What about treating noble gases as a sum type?
```haskell ignore
data Noble
= He
| Ne
| Ar
| Kr
| Xe
| Rn
```
---
`a -> String`
^
???
^
`Noble -> String`
---
```haskell
class Noble a where
toColor :: a -> String
```
~
```haskell
instance Noble He where
toColor _ = "orange"
instance Noble Ne where
toColor _ = "red"
instance Noble Ar where
toColor _ = "lavender"
```
---
Works:
```haskell ignore
λ> toColor He
"orange"
λ> toColor Ne
"red"
```
Doesn't work:
```haskell ignore
λ> toColor C
<interactive>:6:1: error:
• No instance for (Noble C) arising from a use of toColor
• In the expression: toColor C
In an equation for it: it = toColor C
```
---
```haskell
mixNoble :: (Noble n1, Noble n2) => n1 -> n2 -> String
mixNoble n1 n2 = toColor n1 <> "-" <> toColor n2
```
```haskell ignore
λ> mixNoble He Ne
"orange-red"
```
-----
## Shrodinger's cat
![Cat in a box](/static/content/slides/haskell-molecules/cat.png)
2024-02-04 01:00:25 +01:00
---
![Cat in a box](/static/content/slides/haskell-molecules/cat.png)
2024-02-04 01:00:25 +01:00
```haskell
data Box a
= Has a
| Empty
deriving Show
2024-02-04 01:00:25 +01:00
```
---
![Map](/static/content/slides/haskell-molecules/map.png)
---
![Map](/static/content/slides/haskell-molecules/map.png)
2024-02-04 01:00:25 +01:00
```haskell
class Mappable box where
map' :: (a -> b) -> box a -> box b
2024-02-04 01:00:25 +01:00
```
---
```haskell ignore
class Mappable box where
map' :: (a -> b) -> box a -> box b
```
2024-02-04 01:00:25 +01:00
```haskell
instance Mappable Box where
map' _ Empty = Empty
map' f (Has a) = Has (f a)
instance Mappable [] where
map' _ [] = []
map' f xs = [f x | x <- xs]
```
---
```haskell ignore
instance Mappable [] where
map' _ [] = []
map' f xs = [f x | x <- xs]
```
![Cat list](/static/content/slides/haskell-molecules/cat-list.png)
-----
![Cat merge](/static/content/slides/haskell-molecules/cat-merge.png)
---
![Cat merge](/static/content/slides/haskell-molecules/cat-merge.png)
```haskell
data Cat = Cat String deriving Show
data Dog = Dog String deriving Show
merge :: Cat -> Cat -> Dog
merge (Cat c1) (Cat c2) = Dog $ c1 <> " & " <> c2
```
---
```haskell
catA = Cat "Meow"
catB = Cat "Nyaa"
```
```haskell ignore
λ> merge catA catB
Dog "Meow & Nyaa"
```
---
```haskell
boxA = Has $ Cat "Meow" :: Box Cat
boxB = Has $ Cat "Nyaa" :: Box Cat
empty = Empty :: Box Cat
```
---
```haskell ignore
boxA = Has $ Cat "Meow" :: Box Cat
boxB = Has $ Cat "Nyaa" :: Box Cat
empty = Empty :: Box Cat
```
```haskell ignore
λ> merge boxA catB
<interactive>:7:7: error:
• Couldn't match expected type Cat with actual type Box Cat
• In the first argument of merge, namely boxA
In the expression: merge boxA catB
In an equation for it: it = merge boxA catB
```
---
```haskell ignore
boxA = Has $ Cat "Meow" :: Box Cat
boxB = Has $ Cat "Nyaa" :: Box Cat
empty = Empty :: Box Cat
```
```haskell
merge' :: Box Cat -> Box Cat -> Box Dog
merge' Empty _ = Empty
merge' _ Empty = Empty
merge' (Has c1) (Has c2) = Has $ merge c1 c2
```
---
```haskell ignore
merge' :: Box Cat -> Box Cat -> Box Dog
merge' Empty _ = Empty
merge' _ Empty = Empty
merge' (Has c1) (Has c2) = merge c1 c2
```
```haskell ignore
λ> merge' boxA boxB
Has (Dog "Meow & Nyaa")
λ> merge' boxA (Has catB)
Has (Dog "Meow & Nyaa")
λ> merge' boxA empty
Empty
```
---
```haskell
class Appliable box where
wrap :: a -> box a
apply' :: box (a -> b) -> box a -> box b
```
---
```haskell ignore
2024-02-04 01:00:25 +01:00
class Appliable box where
wrap :: a -> box a
apply' :: box (a -> b) -> box a -> box b
```
```haskell
instance Appliable Box where
wrap = Has
apply' Empty _ = Empty
apply' _ Empty = Empty
apply' (Has f) (Has a) = Has $ f a
instance Appliable [] where
wrap x = [x]
apply' [] _ = []
apply' _ [] = []
apply' fs xs = [f x | f <- fs, x <- xs]
```
---
```haskell ignore
instance Appliable Box where
wrap = Has
apply' Empty _ = Empty
apply' _ Empty = Empty
apply' (Has f) (Has a) = Has $ f a
instance Appliable [] where
wrap x = [x]
apply' [] _ = []
apply' _ [] = []
apply' fs xs = [f x | f <- fs, x <- xs]
```
```haskell ignore
λ> apply' (apply' (wrap merge) boxA) boxB
Has (Dog "Meow & Nyaa")
λ> apply' (apply' (wrap merge) boxA) empty
Empty
```
---
```haskell ignore
instance Appliable Box where
wrap = Has
apply' Empty _ = Empty
apply' _ Empty = Empty
apply' (Has f) (Has a) = Has $ f a
instance Appliable [] where
wrap x = [x]
apply' [] _ = []
apply' _ [] = []
apply' fs xs = [f x | f <- fs, x <- xs]
```
```haskell ignore
λ> apply' (apply' (wrap merge) [Cat "A"]) [Cat "B1", Cat "B2"]
[Dog "A & B1",Dog "A & B2"]
λ> apply' (apply' (wrap merge) [Cat "A"]) []
[]
```
---
```haskell ignore
λ> apply' (apply' (wrap merge) [Cat "A"]) [Cat "B1", Cat "B2"]
[Dog "A & B1",Dog "A & B2"]
λ> apply' (apply' (wrap merge) [Cat "A"]) []
[]
```
```haskell ignore
λ> wrap merge `apply'` [Cat "A"] `apply'` [Cat "B1", Cat "B2"]
[Dog "A & B1",Dog "A & B2"]
λ> wrap merge `apply'` [Cat "A"] `apply'` []
[]
```
---
```haskell ignore
λ> wrap merge `apply'` [Cat "A"] `apply'` [Cat "B1", Cat "B2"]
[Dog "A & B1",Dog "A & B2"]
λ> wrap merge `apply'` [Cat "A"] `apply'` []
[]
```
```haskell ignore
λ> wrap merge `apply'` boxA `apply'` boxB
Has (Dog "Meow & Nyaa")
λ> wrap merge `apply'` boxA `apply'` empty
Empty
```
2024-02-04 01:00:25 +01:00
---
```haskell
merge4 :: Cat -> Cat -> Cat -> Cat -> Dog
merge4 (Cat a) (Cat b) (Cat c) (Cat d) = Dog $ a <> b <> c <> d
```
```haskell ignore
λ> wrap merge4 `apply'` boxA `apply'` boxB `apply'` boxA `apply'` boxB
Has (Dog "MeowNyaaMeowNyaa")
λ> wrap merge4 `apply'` boxA `apply'` boxB `apply'` empty `apply'` boxB
Empty
```
-----
```haskell
2024-02-04 15:20:15 +01:00
kill :: Cat -> Box Cat
kill _ = Empty
2024-02-04 15:20:15 +01:00
save :: Cat -> Box Cat
save c = Has c
```
2024-02-04 15:20:15 +01:00
<small>(no animals were harmed in the making of this slideshow)</small>
---
```haskell ignore
kill :: Cat -> Box Cat
kill _ = Empty
save :: Cat -> Box Cat
save c = Has c
```
![Kill](/static/content/slides/haskell-molecules/kill.png)
---
![Kill](/static/content/slides/haskell-molecules/kill.png)
```haskell ignore
λ> wrap kill `apply'` boxA
Has Empty
λ> wrap save `apply'` boxA
Has (Has (Cat "Meow"))
```
---
2024-02-04 01:00:25 +01:00
```haskell
class Chainable box where
chain :: box a -> (a -> box b) -> box b
```
2024-02-04 15:20:15 +01:00
---
```haskell ignore
class Chainable box where
chain :: box a -> (a -> box b) -> box b
```
```haskell
instance Chainable Box where
chain Empty _ = Empty
chain (Has a) f = f a
instance Chainable [] where
chain [] _ = []
chain xs f = [x' | x <- xs, x' <- f x]
```
---
```haskell ignore
instance Chainable Box where
chain Empty _ = Empty
chain (Has a) f = f a
instance Chainable [] where
chain [] _ = []
chain xs f = [x' | x <- xs, x' <- f x]
```
![Save-kill](/static/content/slides/haskell-molecules/save-kill.png)
---
![Save-kill](/static/content/slides/haskell-molecules/save-kill.png)
```haskell ignore
λ> boxA `chain` save `chain` save `chain` save
Has (Cat "Meow")
λ> boxA `chain` save `chain` kill `chain` save
Empty
```
---
```haskell
kill' :: Cat -> [Cat]
kill' _ = []
save' :: Cat -> [Cat]
save' c = [c]
clone :: Cat -> [Cat]
clone (Cat c) = [Cat $ c <> "L" , Cat $ c <> "R"]
```
---
```haskell ignore
kill' :: Cat -> [Cat]
kill' _ = []
save' :: Cat -> [Cat]
save' c = [c]
2024-02-04 01:00:25 +01:00
2024-02-04 15:20:15 +01:00
clone :: Cat -> [Cat]
clone (Cat c) = [Cat $ c <> "L" , Cat $ c <> "R"]
```
```haskell ignore
λ> [catA] `chain` save' `chain` save' `chain` save'
[Cat "Meow"]
λ> [catA] `chain` save' `chain` clone `chain` save'
[Cat "MeowL",Cat "MeowR"]
λ> [catA] `chain` more' `chain` clone `chain` save'
[Cat "MeowLL",Cat "MeowLR",Cat "MeowRL",Cat "MeowRR"]
λ> [catA] `chain` clone `chain` clone `chain` kill'
[]
```
---
```haskell ignore
λ> [catA] `chain` save' `chain` save' `chain` save'
[Cat "Meow"]
λ> [catA] `chain` save' `chain` clone `chain` save'
[Cat "MeowL",Cat "MeowR"]
λ> [catA] `chain` more' `chain` clone `chain` save'
[Cat "MeowLL",Cat "MeowLR",Cat "MeowRL",Cat "MeowRR"]
λ> [catA] `chain` clone `chain` clone `chain` kill'
[]
```
2024-02-04 01:00:25 +01:00
2024-02-04 15:20:15 +01:00
![Chain list](/static/content/slides/haskell-molecules/chain-list.png)
---
![Callbacks](/static/content/slides/haskell-molecules/callbacks.jpg)
-----
![This was all dream](/static/content/slides/haskell-molecules/cave.png)
2024-02-04 01:00:25 +01:00
---
What's a `Mappable`?
---
2024-02-04 15:20:15 +01:00
It's a `Functor`
![It's a Functor](/static/content/slides/haskell-molecules/its-functor.png)
---
It's a `Functor`
```haskell ignore
class Mappable box where
map' :: (a -> b) -> box a -> box b
```
```haskell ignore
class Functor f where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
```
---
It's a `Functor`
```haskell ignore
λ> map' (+1) []
[]
λ> map' (+1) [1, 2, 3]
[2,3,4]
λ> map' (*3) [1, 2, 3]
[3,6,9]
```
```haskell ignore
λ> fmap (+1) []
[]
λ> fmap (+1) [1, 2, 3]
[2,3,4]
λ> fmap (*3) [1, 2, 3]
[3,6,9]
λ> 1 <$ [1, 2, 3]
[1,1,1]
```
---
It's a `Functor`
![Tardis](/static/content/slides/haskell-molecules/tardis.jpg)
---
It's a `Functor`
![Tardis inside](/static/content/slides/haskell-molecules/tardis-inside.jpg)
---
```haskell ignore
main :: IO ()
main = print "Hello World!"
```
![Tardis inside](/static/content/slides/haskell-molecules/io.png)
2024-02-04 01:00:25 +01:00
---
What's a `Appliable`?
---
2024-02-04 15:20:15 +01:00
It's an Applicative
![It's an applicative](/static/content/slides/haskell-molecules/its-applicative.png)
---
It's an Applicative
```haskell ignore
class Appliable box where
wrap :: a -> box a
apply' :: box (a -> b) -> box a -> box b
```
```haskell ignore
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
```
---
It's an Applicative
```haskell ignore
λ> wrap merge `apply'` [catA] `apply'` []
[]
λ> wrap merge `apply'` [catA] `apply'` [catB]
[Dog "Meow & Nyaa"]
λ> wrap merge `apply'` [catA] `apply'` [catB, Cat "C"]
[Dog "Meow & Nyaa",Dog "Meow & C"]
```
```haskell ignore
λ> pure merge <*> [catA] <*> []
[]
λ> pure merge <*> [catA] <*> [catB]
[Dog "Meow & Nyaa"]
λ> pure merge <*> [catA] <*> [catB, Cat "C"]
[Dog "Meow & Nyaa",Dog "Meow & C"]
```
---
```haskell ignore
λ> pure merge <*> [catA] <*> []
[]
λ> pure merge <*> [catA] <*> [catB]
[Dog "Meow & Nyaa"]
λ> pure merge <*> [catA] <*> [catB, Cat "C"]
[Dog "Meow & Nyaa",Dog "Meow & C"]
```
```haskell ignore
λ> merge <$> [catA] <*> []
[]
λ> merge <$> [catA] <*> [catB]
[Dog "Meow & Nyaa"]
λ> merge <$> [catA] <*> [catB, Cat "C"]
[Dog "Meow & Nyaa",Dog "Meow & C"]
```
```haskell ignore
merge :: Cat -> Cat -> Dog
_ = merge catA catB
```
2024-02-04 01:00:25 +01:00
---
What's a `Chainable`?
---
2024-02-04 15:20:15 +01:00
It's a monad
![It's a monad](/static/content/slides/haskell-molecules/its-monad.png)
---
It's a monad
```haskell ignore
class Chainable box where
chain :: box a -> (a -> box b) -> box b
```
```haskell ignore
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
m >> k = m >>= \_ -> k
return :: a -> m a
return = pure
```
2024-02-04 01:00:25 +01:00
---
2024-02-04 15:20:15 +01:00
It's a monad
2024-02-04 01:00:25 +01:00
2024-02-04 15:20:15 +01:00
```haskell ignore
λ> [catA] `chain` save' `chain` save' `chain` save'
[Cat "Meow"]
λ> [catA] `chain` save' `chain` clone `chain` save'
[Cat "MeowL",Cat "MeowR"]
λ> [catA] `chain` save' `chain` clone `chain` kill'
[]
```
```haskell ignore
λ> [catA] >>= save' >>= save' >>= save'
[Cat "Meow"]
λ> [catA] >>= save' >>= clone >>= save'
[Cat "MeowL",Cat "MeowR"]
λ> [catA] >>= save' >>= clone >>= kill'
[]
```
2024-02-04 01:00:25 +01:00
---
2024-02-04 15:20:15 +01:00
![Just one more typeclass bro](/static/content/slides/haskell-molecules/pepe.png)
-----
2024-02-04 01:00:25 +01:00
## More here:
- **Learn You a Haskell for Great Good!** by Miran Lipovaca
- **Programming in Haskell - 2nd Edition** by Graham Hutton
- **Effective Haskell** by Rebecca Skinner