class: center, middle, title-slide count: false  .less-line-height[ Alejandro Serrano @ INFOFP 2023-24 .grey[๐ฆ @trupill - ๐จโ๐ป JetBrains
`serranofp.com`] ] --- # In previous years... ``` Your knowledge of C# + Your knowledge of Haskell --------------------------------- Ready for mainstream programming! ``` --- # In previous years... ``` Your knowledge of C# + Your knowledge of Haskell --------------------------------- Ready for mainstream programming! ``` Last decade has been a good one for programing languages - Java or C# were stuck in 2000s - Now we have Scala, Kotlin, Swift, Elixir! - And Java and C# are catching up --- # Higher order functions are just there ### .grey[You understand this!] From the Java documentation .code70[ ```java int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight()) .sum(); ``` ] .font60[ Reference:
`https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html` ] --- # In previous years... ``` Your knowledge of C# + Your knowledge of Haskell --------------------------------- Ready for mainstream programming! ``` > Most of these ideas come from FP INFOFP has become really relevant -- ## **Let's do something else instead!** --- #
Pokรฉmon Trading Card Game Players take turns drawing and playing cards
Goal: knock out 6 of your opponent's Pokรฉmon
For this your use
attacks
Those attacks cost
energy
Each attack does
damage
HP
define the maximum damage before knock-out
--- # ๐๏ธ Our approach ### .grey[Explanations interleaved with tasks] 1. Representing cards 2. Representing actions 3. Testing actions -- ## .grey[Domain-specific Language (DSL)] Implementation of the _Ubiquitous Language_ idea from DDD, the code speaks the domain --- #
Representing cards
Each card comes with...
Name:
Pikachu
Type:
HP: 70
Attack(s)
(forget about the rest for now)
--- # โ๏ธ Algebraic Data Types (ADTs)
Each card comes with...
Name:
Pikachu
Type:
HP: 70
Attack(s)
Straightforward translation of the description .code70[ ```haskell data Card = Card { name :: Text , typ :: Energy , hp :: Natural , attacks :: [Attack] } ``` ] --- # โ๏ธ Algebraic Data Types (ADTs)
Each card comes with...
Name:
Pikachu
Type:
HP: 70
Attack(s)
.code70[ ```haskell data Card = Card { name :: Text , typ :: Energy , hp :: HP , attacks :: [Attack] } newtype HP = HP Natural deriving (Eq, Show, Num) ``` ] --- # โฏ๏ธ Energies There are 10 types of energy in the game, - 9 regular energies
- Colorless energy
- Any card providing a regular energy may also provide colorless energy
= 2
+ 1 of any other --- # โฏ๏ธ Energies There are 10 types of energy in the game, - 9 regular energies
- Colorless energy
.code70[ ```haskell data Energy = Colorless | Grass | Fire | Water | Lightning | Fighting | Psychic | Darkness | Metal | Dragon data Card = PokemonCard { ... } | EnergyCard { typ :: Energy } ``` ] --- # โ๏ธ Attacks We consider only "simple" attacks for now
.code70[ ```haskell data Attack = Attack { attackName :: Text , cost :: [Energy] , damage :: Natural } ``` ] --- # ๐งโ๐ป Time for practice! .very-little-margin-top[ ### `serranofp.com/infofp.zip` ] Define values for the following cards
--- # โ๏ธ Attacks, redux .code70[ ```haskell data Attack = Attack { ..., damage :: Natural } ``` ] ## .grey[This is a ~~lie~~ simplification] --- # โ๏ธ Attacks, redux
--- # โ๏ธ Attacks, redux .code70[ ```haskell data Attack = Attack { ..., damage :: Natural } ``` ] ## .grey[This is a ~~lie~~ simplification] .top-margin[ - More actions than mere damage - Draw and discard cards - Actions may depend on the state - Attached cards - Coin flips - Actions may involve conditionals and loops ] --- # โ๏ธ Attacks, redux .code70[ ```haskell data Attack = Attack { ..., action :: ??? } ``` ] ## How do we model .grey[actions]? .top-margin[ - More actions than mere damage - Draw and discard cards - Actions may depend on the state - Attached cards - Coin flips - Actions may involve conditionals and loops ] --- # ๐ช Coin flips
.code70[ ```haskell data FlipOutcome = Heads | Tails data Action = FlipCoin (FlipOutcome -> Action) | Damage Natural surpriseAttackAction = FlipCoin $ \case Heads -> Damage 30 Tails -> Damage 0 ``` ] --- # ๐ช Coin flips
## .grey[๐งโ๐ป Time for practice!] .font70[`serranofp.com/infofp.zip`] --- # ๐ช Coin flips
```haskell ironTailAction = go 0 where go acc = FlipCoin $ \case Tails -> Damage acc Heads -> go (acc + 30) ``` --- # ๐ง Syntax/algebra and interpretation `Action` defines the **syntax** of our DSL
(also known as **algebra** in some circles) > "The language itself", "what we can say" --- # ๐ง Syntax/algebra and interpretation `Action` defines the **syntax** of our DSL
(also known as **algebra** in some circles) An **interpretation** defines how each value behaves in a certain context > "What a sentence means" 1 syntax / algebra โท โ interpretations --- # ๐ฐ Randomness interpretation During the actual game, we expect to generate random coin flips to obtain the actual damage ```haskell interpretRandom :: Action -> IO Natural ``` ## .grey[๐งโ๐ป Time for practice!] .font70[`serranofp.com/infofp.zip`] --- # ๐ฐ Randomness interpretation During the actual game, we expect to generate random coin flips to obtain the actual damage ```haskell interpretRandom :: Action -> IO Natural interpretRandom (Damage d) = pure d interpretRandom (FlipCoin f) = do outcome <- flipCoin interpretRandom (f outcome) -- one-liner -- flipCoin >>= interpretRandom . f ``` --- # ๐ด Actions about cards
--- # ๐ด Actions about cards .code70[ ```haskell data Action = FlipCoin (FlipOutcome -> Action) | DrawCard (Maybe Card -> Action) -- ^ there may not be more cards | QueryAttached ([Card] -> Action) -- ^ get info. about the current Pokรฉmon | Damage Natural ``` ] ### .grey[Can you spot the pattern? ๐] --- # ๐ด Actions about cards .code70[ ```haskell data Action = FlipCoin (FlipOutcome -> Action) | DrawCard (Maybe Card -> Action) -- ^ there may not be more cards | QueryAttached ([Card] -> Action) -- ^ get info. about the current Pokรฉmon | Damage Natural ``` ] - `Damage` is a **final** action - The rest "generate" a value,
which is consumed to keep going --- # ๐ด Actions about cards
## .grey[๐งโ๐ป Time for practice!] .font70[`serranofp.com/infofp.zip`] .margin-top[ 1. Write a function to **draw *n* ** cards 2. Add an additional operation to **discard** cards - Must include a predicate to select cards - Outcome: whether a card was discarded ] --- # ๐ด Actions about cards ## .grey[๐งโ๐ป Time for practice!] .font70[`serranofp.com/infofp.zip`] Write a function to **draw *n* ** cards _What should be the function signature?_ -- ```haskell drawN :: Natural -- amount -> ([Card] -> Action) -- "next" -> Action drawN n next = _ ``` --- # ๐ด Actions about cards Write a function to **draw *n* ** cards .code70[ ```haskell drawN n next = go n [] where go n acc | n <= 0 = next (reverse acc) | otherwise = DrawCard $ \case Nothing -> next (reverse acc) Just c -> go (n - 1) (c : acc) ``` ] --- # ๐ฉป Property-based testing Generate many _random_ tests for the same function (or set of them) Focus on **properties** rather than examples - PBT frameworks are good at generating corner cases (extreme values, empty lists, ...) --- # โ Testing actions ### .grey[How can we test `ironTailAction`?]
--- # โ Testing actions ### .grey[How can we test `ironTailAction`?]
- If we get a tail as first result, we get 0 - If our outcomes start with `n` heads,
then the result is `30 * n` --- # โ Testing actions ### .grey[How can we test `ironTailAction`?]
โ Using `interpretRandom` would not work - The outcome is random - Testing `IO` is cumbersome --- # ๐งฎ Pure interpretation of flipping We pass the future outcomes as a parameter ```haskell interpretPure :: [FlipOutcome] -> Action -> Natural ``` --- # ๐งฎ Pure interpretation of flipping We pass the future outcomes as a parameter ```haskell interpretPure :: [FlipOutcome] -> Action -> Natural ``` Now we control the future ๐ฎ ```haskell > interpretPure [Heads, Heads, Tails] ironTailAction 60 ``` --- # ๐งฎ Pure interpretation of flipping .code70[ ```haskell interpretPure :: [FlipOutcome] -> Action -> Natural interpretPure (result : future) (FlipCoin next) = interpretPure future (next result) interpretPure _future (Damage n) = n ``` ] --- # ๐ QuickCheck + Tasty ๐ฅง **QuickCheck** is a well-known library for property-based testing - Define properties of functions - Support for custom generators **Tasty** is a test runner - Runs and reports over a set of tests --- # ๐ QuickCheck + Tasty ๐ฅง ๐ฅง `testGroup` + ๐ `testProperty` - `outcomes` is randomly selected .code70[ ```haskell tests :: TestTree tests = testGroup "Iron Tail" [ testProperty "non-negative" $ \outcomes -> interpretPure (outcomes ++ [Tails]) ironTailAction >= 0 , ... ] ``` ] --- # โ A wrong property ```haskell interpretPure ... ironTailAction > 0 ``` A counter-example is found by QuickCheck .code70[ ``` Iron Tail non-negative: FAIL *** Failed! Falsified (after 1 test): [] Use --quickcheck-replay=139730 to reproduce. Use -p '/non-negative/' to rerun this test only. ``` ] --- #
The "times 30" property To create good properties you must... - Be creative with the inputs - Ensure that inputs are correct .code70[ ```haskell testProperty "30 * # heads" $ \(hs :: Int) -> hs > 0 ==> let outcomes = replicate hs Heads ++ [Tails] in interpretPure outcomes ironTailAction == fromIntegral (hs * 30) ```] --- class: center, middle, title-slide count: false # ๐ป Spooky slides ahead --- # ๐ This is terrible
```haskell ironTailAction = go 0 where go acc = FlipCoin $ \case Tails -> Damage acc Heads -> go (acc + 30) ``` --- # ๐ This reads better
```haskell ironTailAction = do heads <- while (/= Tails) flipCoin return (30 * length heads) ``` -- ## We are building our own .grey[language] --- # ๐ง Monads! ```haskell data Action = FlipCoin (FlipOutcome -> Action) | Damage Natural ``` Why is it **not** possible to write `Monad` for this? -- `Monad` applies to .grey[**type constructors**] - We need to turn this into `Action a` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation ``` -- ## .grey[๐งโ๐ป Time for practice!] .font70[`serranofp.com/infofp.zip`] Write the `Monad Action` instance --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return = _ x >>= f = _ ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where -- a -> Action a return = _ x >>= f = _ ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where -- a โฐ โฑ Action a return x = _ x >>= f = _ ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where -- a โฐ โฑ Action a return x = Return x x >>= f = _ ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return x = Return x -- Action a -> (a -> Action b) -> Action b x >>= f = _ ``` -- Pattern matching on `Action` keeps us going --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return x = Return x -- a โฐ โฑ (a -> Action b) Return x >>= f = _ -- Action b FlipCoin next >>= f = _ ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return x = Return x -- a โฐ โฑ (a -> Action b) Return x >>= f = f x -- Action b FlipCoin next >>= f = _ ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return x = Return x Return x >>= f = f x -- Action b -- FlipCoin -> Action a -- โ โฑ (a -> Action b) FlipCoin next >>= f = _ ``` -- We are mixing two blocks of actions --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return x = Return x Return x >>= f = f x -- Action b -- FlipCoin -> Action a -- โ โฑ (a -> Action b) FlipCoin next >>= f = FlipCoin _ -- FlipCoin -> Action b โต ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return x = Return x Return x >>= f = f x -- Action b -- FlipCoin -> Action a -- โ โฑ (a -> Action b) FlipCoin next >>= f = FlipCoin (\oc -> _ :: Action b) ``` --- # ๐ง Monads! ```haskell data Action a = FlipCoin (FlipOutcome -> Action a) | Return a -- "ends" the computation instance Monad Action where return x = Return x Return x >>= f = f x -- Action b -- FlipCoin -> Action a -- โ โฑ (a -> Action b) FlipCoin next >>= f = FlipCoin (\oc -> next oc >>= f) ``` --- # ๐ฎโ๐จ What have we gained? Being a `Monad` gives you `do` notation ```haskell flipCoin :: Action FlipOutcome flipCoin = FlipCoin Return flipTwo = do x <- flipCoin y <- flipCoin if (x == Heads && y == Heads) then return 50 else return 0 ``` --- # ๐ฎโ๐จ What have we gained? Being a `Monad` gives you `do` notation ```haskell flipTwo = FlipCoin $ \oc1 -> FlipCoin $ \oc2 -> if (oc1 == Heads && oc2 == Heads) then Return 50 else Return 0 ``` No need to handle constructors manually! --- # ๐ฎโ๐จ What have we gained? Being a `Monad` gives you access to many others - Everything in `Control.Monad` - `Control.Monad.Extra` in `extra` - `monad-loops` -- ```haskell ironTailAction = do heads <- while (/= Tails) flipCoin return (30 * length heads) ``` --- # ๐ Summary ### .grey[Haskell is a great language for DSLs] .margin-top[ - ADTs model the domain sharply - We can model both data and processes - One model, many interpretations - Useful for (property-based) testing ] --- # ๐ค๏ธ Where to go from here? .grey[**Domain Driven Design**] is a design approach based on understanding and sharing a _language_ with the client - Increasingly popular in industry - Good fit with functional programming ๐ _Domain Modeling Made Functional_
by Scott Wlaschin --- # ๐ค๏ธ Where to go from here? .grey[**Integration tests**] look at the interaction between layers (one level above unit tests) ### ๐ตโ๐ซ Dependencies on external services .margin-top[ - "Purify" the dependency (eliminate it) - Mocks and stubs (simulate it) - Test containers (bundle it) ] --- class: center, middle, title-slide # ๐คฉ It's been a pleasure ## Go and tell everybody about Haskell!