--- 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 () | Bool | 1 + 2 = 3 H' | Bool | 3 + 2 = 5 ```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 :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) --- ![Cat in a box](/static/content/slides/haskell-molecules/cat.png) ```haskell data Box a = Has a | Empty deriving Show ``` --- ![Map](/static/content/slides/haskell-molecules/map.png) --- ![Map](/static/content/slides/haskell-molecules/map.png) ```haskell class Mappable box where map' :: (a -> b) -> box a -> box b ``` --- ```haskell ignore class Mappable box where map' :: (a -> b) -> box a -> box b ``` ```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 :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 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 ``` --- ```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 kill :: Box Cat -> Box Cat kill _ = Empty save :: Box Cat -> Box Cat save = id ``` ```haskell class Chainable box where chain :: box a -> (a -> box b) -> box b ``` ----- In fact it all already exists. ![This was all dream](/static/content/slides/haskell-molecules/_cave.jpg) --- What's a `Mappable`? --- ![It's a Functor](/static/content/slides/haskell-molecules/scoobydoo.jpg) --- What's a `Appliable`? --- ![It's a Functor](/static/content/slides/haskell-molecules/scoobydoo.jpg) --- What's a `Chainable`? --- ![It's a Functor](/static/content/slides/haskell-molecules/scoobydoo.jpg) --- ## Fun with monadic parsing --- ## 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