From e56c43d73ddc2e6764f486f70b522a079cc38bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Hurlin?= Date: Fri, 11 Oct 2024 12:03:29 +0200 Subject: [PATCH] Finish course 5. Introduce GADTs before type families. --- slides/Course05.hs | 30 ++++++++++++-- slides/course-05.md | 98 ++++++++++++++++++++++++++++++++++++++++----- tn-fp-course.cabal | 2 + tps/Scratch.hs | 43 +++++++++++++++++++- 4 files changed, 157 insertions(+), 16 deletions(-) diff --git a/slides/Course05.hs b/slides/Course05.hs index 030ac1a..5f04253 100644 --- a/slides/Course05.hs +++ b/slides/Course05.hs @@ -56,11 +56,33 @@ class (Monoid w, Monad m) => MonadWriter w m | m -> w where writerExample :: MonadWriter String m => m (Maybe Int) writerExample = do - a <- pure $ Just 1 -- Could be an IO action - tell ("Producing a" :: String) - b <- pure $ Just 2 + a <- pure (Just 1) -- Could be an IO action + tell "Producing a" + b <- pure Nothing tell "Producing b" - return $ (+) <$> a <*> b + let r = (+) <$> a <*> b + return r + +data Mix = MkMix { file :: FilePath, hash :: Int } + +-- Phantom types +data File +data Hash + +data Role a where + FileRole :: Role File + HashRole :: Role Hash + +type family DataKind a where + DataKind File = FilePath + DataKind Hash = Int + +-- | @getData m r@ can return either a value of type @FilePath@ +-- or a value of type @Int@! +getData :: Mix -> Role a -> DataKind a +getData mix = \case + FileRole -> mix.file + HashRole -> mix.hash class Monad m => MonadState s m | m -> s where -- | Return the state diff --git a/slides/course-05.md b/slides/course-05.md index 4038b4c..5adf500 100644 --- a/slides/course-05.md +++ b/slides/course-05.md @@ -156,14 +156,8 @@ class Applicative m => Monad m where --
-
-Generic model of computation - -
-
- -Plug monadic code with pure code + Generic model of interruptible computation
--- @@ -233,25 +227,107 @@ writerExample = do tell "Producing a" b <- pure Nothing tell "Producing b" - return $ (+) <$> a <*> b + let r = (+) <$> a <*> b + return r +``` + +--- + +# Generalized Algebraic Datatypes (GADTs) + +The problem: + + +```hs +data Expr a b = + IntExpr Int + | BoolExpr Bool + | PairExpr a b + | IfThenElseExpr Bool (Expr a b) (Expr a b) + +eval :: Expr a b -> ? +``` + +
+🤮 +
+ +-- + +GADTs to the rescue! + + +```hs +data Expr t where + IntExpr :: Int -> Expr Int + BoolExpr :: Bool -> Expr Bool + Pair :: Expr a -> Expr b -> Expr (a, b) + IfThenElse :: Expr Bool -> Expr a -> Expr a -> Expr a + +eval :: Expr a -> a ``` +
+😎 +
+ --- # Type Families +* Type families are type-level functions +* Functions that takes types as inputs and produce types + +
+Type families' main usage is to work with GADTs +
+ +-- + +```hs +data Mix = MkMix { file :: FilePath, hash :: Int } + +-- Phantom types +data File +data Hash + +data Role a where + FileRole :: Role File + HashRole :: Role Hash + +type family DataKind a where + DataKind File = FilePath + DataKind Hash = Int + +-- | @getData m r@ can return either a value of type @FilePath@ +-- or a value of type @Int@! +getData :: Mix -> Role a -> DataKind a +getData mix = \case + FileRole -> mix.file + HashRole -> mix.hash +``` + --- # Recap -TBD +* Monads + * Generic sequencing operation, supporting interruption + * Safely bring back sequencing programming to functional programming 👑 +* GADTs + * Existential types within data constructors + * Fine-grained typing of different constructors +* Type families + * Uniform treatment of non-uniform types --- # Recommended reading -* [MonadWriter](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-Writer-CPS.html#t:MonadWriter) -* [MonadState](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-State-Class.html#t:MonadState) +* On Hackage: + * [MonadWriter](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-Writer-CPS.html#t:MonadWriter) + * [MonadState](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-State-Class.html#t:MonadState) +* Type families: https://serokell.io/blog/type-families-haskell --- diff --git a/tn-fp-course.cabal b/tn-fp-course.cabal index d2acd2f..8b5bd77 100644 --- a/tn-fp-course.cabal +++ b/tn-fp-course.cabal @@ -44,6 +44,7 @@ common common-all FlexibleContexts FlexibleInstances FunctionalDependencies + GADTs MultiParamTypeClasses NamedFieldPuns LambdaCase @@ -52,6 +53,7 @@ common common-all RecordWildCards ScopedTypeVariables TypeApplications + TypeFamilies library slides import: common-all diff --git a/tps/Scratch.hs b/tps/Scratch.hs index ee00421..92a365b 100644 --- a/tps/Scratch.hs +++ b/tps/Scratch.hs @@ -2,6 +2,8 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# OPTIONS_GHC -Wno-noncanonical-monad-instances #-} {-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE TypeFamilies #-} -- | Module for live trial and error module Scratch where @@ -153,4 +155,43 @@ monadWriterExample = do tell "Producing a" b <- pure $ Nothing tell "Producing b" - return $ (+) <$> a <*> b \ No newline at end of file + return $ (+) <$> a <*> b + +data Expr t where + IntExpr :: Int -> Expr Int + BoolExpr :: Bool -> Expr Bool + Pair :: Expr a -> Expr b -> Expr (a, b) + IfThenElse :: Expr Bool -> Expr a -> Expr a -> Expr a + + +eval :: Expr a -> a +eval = \case + IntExpr x -> x + BoolExpr x -> x + Pair a b -> (eval a, eval b) + IfThenElse cond a b -> if eval cond then eval a else eval b + +-- data Expr a b = +-- IntExpr Int +-- | BoolExpr Bool +-- | PairExpr a b +-- | IfThenElseExpr Bool (Expr a b) (Expr a b) + +data Mix = MkMix { file :: FilePath, hash :: Int } + +-- Phantom types +data File +data Hash + +data Role a where + FileRole :: Role File + HashRole :: Role Hash + +type family DataKind a where + DataKind File = FilePath + DataKind Hash = Int + +getData :: Mix -> Role a -> DataKind a +getData mix = \case + FileRole -> mix.file + HashRole -> mix.hash \ No newline at end of file