Slide 1

Slide 1 text

SUPERCHARGED IMPERATIVE PROGRAMMING WITH HASKELL AND FP ANUPAM JAIN

Slide 2

Slide 2 text

2 Hello!

Slide 3

Slide 3 text

3 ❑ HOME PAGE
 https://fpncr.github.io ❑ GETTOGETHER COMMUNITY
 https://gettogether.community/fpncr/ ❑ MEETUP
 https://www.meetup.com/DelhiNCR-Haskell-And- Functional-Programming-Languages-Group ❑ TELEGRAM:
 https://t.me/fpncr Functional Programming NCR

Slide 4

Slide 4 text

4 ❑Type safety. Eliminates a large class of errors. ❑Effectful values are first class ❑Higher Order Patterns ❑Reduction in Boilerplate ❑Zero Cost Code Reuse Overview

Slide 5

Slide 5 text

5 ❑Order of operations matters ❑Contrast with functional, where the order of operations does not matter. Define “Imperative”

Slide 6

Slide 6 text

6 write "Do you want a pizza?” if (read() == "Yes") orderPizza() write "Should I launch missiles?” if (read() == "Yes") launchMissiles() Imperative is simple

Slide 7

Slide 7 text

7 write "Do you want a pizza?” if (read() == "Yes") orderPizza() write "Should I launch missiles?” if (read() == "Yes") launchMissiles() Imperative is simple You REALLY DON’T want to do these out of order

Slide 8

Slide 8 text

8 do write "Do you want a pizza?" canOrder <- read When (canOrder == "Yes") orderPizza write "Should I launch missiles?" canLaunch <- read When (canLaunch == "Yes") launchMissiles Functional?

Slide 9

Slide 9 text

9 do write "Do you want a pizza?" canOrder <- read when (canOrder == "Yes") orderPizza write "Should I launch missiles?" canLaunch <- read when (canLaunch == "Yes") launchMissiles Functional? Haskell

Slide 10

Slide 10 text

10 write "Do you want a pizza?" >>= \_ -> read >>= \canOrderPizza -> if (canOrderPizza == "Yes") then orderPizza else pure () >>= \_ -> write "Should I launch missiles?" >>= \_ - > read >>= \canLaunchMissiles -> if (canLaunchMissiles == "Yes") then launchMissiles else pure () Functional?

Slide 11

Slide 11 text

11 plusOne = \x -> x+1 add = \x -> \y -> x+y A bit of syntax Lambdas

Slide 12

Slide 12 text

12 (>>=) = \effect -> \handler -> ... A bit of syntax Operators

Slide 13

Slide 13 text

13 read >>= \canOrderPizza -> ... A bit of syntax Infix Usage

Slide 14

Slide 14 text

14 write "Do you want a pizza?" >>= \_ -> read >>= \canOrderPizza -> if (canOrderPizza == "Yes") then orderPizza else pure () One At a Time

Slide 15

Slide 15 text

15 write "Should I launch missiles?" >>= \_ -> read >>= \canLaunchMissiles -> if (canLaunchMissiles == "Yes") then launchMissiles else pure () One At a Time

Slide 16

Slide 16 text

16 handlePizza >>= \_ -> handleMissiles Together

Slide 17

Slide 17 text

17 handlePizza >>= \_ -> handleMissiles Together

Slide 18

Slide 18 text

18 handlePizza :: IO () handlePizza = do write "Do you want a pizza?" canOrderPizza <- read if (canOrderPizza == "Yes") then orderPizza else pure () Types This entire block 1. Is Effectful 2. Returns ()

Slide 19

Slide 19 text

19 Effectful Logic Pure Logic Outside World

Slide 20

Slide 20 text

20 ❑Can’t mix effectful (imperative) code with pure (functional) code ❑All branches must have the same return type Types

Slide 21

Slide 21 text

21 Side Effects !!

Slide 22

Slide 22 text

22 “Haskell” is the world’s finest imperative programming language. ~Simon Peyton Jones (Creator of Haskell)

Slide 23

Slide 23 text

23 So How is Haskell The Best Imperative Programming Language?

Slide 24

Slide 24 text

24 ❑We don’t launch nukes without ordering pizza Change Requirements

Slide 25

Slide 25 text

25 handlePizza :: IO Bool handlePizza = do write "Do you want a pizza?" canOrderPizza <- read if (canOrderPizza == "Yes") then orderPizza >> pure true else pure false Types

Slide 26

Slide 26 text

26 do pizzaOrdered <- handlePizza if pizzaOrdered then handleMissiles else pure () With Changed Requirements

Slide 27

Slide 27 text

27 ❑Ask the user a bunch of questions ❑Then perform a bunch of actions Reorder?

Slide 28

Slide 28 text

28 Must Rearchitect do write "Do you want a pizza?" canOrder <- read write "Should I launch missiles?" canLaunch <- read when (canOrder == "Yes") orderPizza when (canLaunch == "Yes") launchMissiles

Slide 29

Slide 29 text

29 Must Rearchitect do write "Do you want a pizza?" canOrder <- read write "Should I launch missiles?" canLaunch <- read when (canOrder == "Yes") orderPizza when (canLaunch == "Yes") launchMissiles But we have lost the separation between Ordering pizza and Launching nukes

Slide 30

Slide 30 text

30 We Need ❑Define complex flows with user input and a final effect to be performed ❑To compose these flows without boilerplate ❑Be able to run the final effects together at the end of all user input

Slide 31

Slide 31 text

31 Desired Abstraction handlePizza = ... handleNukes = ... do handlePizza handleNukes We ask questions in this order, but the final effect of ordering pizza and launching nukes should only happen together at the end

Slide 32

Slide 32 text

32 Must Rearchitect handlePizza = do write "Do you want a pizza?" canOrder <- read return $ when (canOrder == "Yes") orderPizza

Slide 33

Slide 33 text

33 Must Rearchitect handlePizza :: IO (IO ()) handlePizza = do write "Do you want a pizza?" canOrder <- read return $ when (canOrder == "Yes") orderPizza Return value is a CLOSURE Captures `canOrder`

Slide 34

Slide 34 text

34 Must Rearchitect handleNukes :: IO (IO ()) handleNukes = do write “Should I launch nukes?" canLaunch <- read return $ when (canLaunch == "Yes") launchNukes Return value is a CLOSURE Captures `canLaunch`

Slide 35

Slide 35 text

35 Compose together do pizzaEffect <- handlePizza nukeEffect <- handleNukes pizzaEffect
 nukeEffect

Slide 36

Slide 36 text

36 Generalises? This looks very boilerplaty do pizzaEffect <- handlePizza nukeEffect <- handleNukes ... pizzaEffect
 nukeEffect ...

Slide 37

Slide 37 text

37 Desired Interface finalEffect =
 handlePizza AND
 handleNukes AND ...

Slide 38

Slide 38 text

38 And Allow A Way to specify “No Effects” finalEffect = emptyEffects

Slide 39

Slide 39 text

39 Looks Like a Monoid! class Monoid M where empty :: M
 (<>) :: M -> M -> M

Slide 40

Slide 40 text

40 IO already is a Monoid! ❑What happens when we do the following? handlePizza <> handleNukes

Slide 41

Slide 41 text

41 IO already is a Monoid! instance Monoid a => Monoid (IO a) where empty = pure empty f <> g = do a <- f b <- g pure (a <> b)

Slide 42

Slide 42 text

42 IO already is a Monoid! instance Monoid a => Monoid (IO a) where empty = pure empty f <> g = do a <- f b <- g pure (a <> b) First perform individual effects

Slide 43

Slide 43 text

43 IO already is a Monoid! instance Monoid a => Monoid (IO a) where empty = pure empty f <> g = do a <- f b <- g pure (a <> b) Then Join the results As Monoids

Slide 44

Slide 44 text

44 IO already is a Monoid! ❑So this does the right thing! do finalEffects <- handlePizza <> handleNukes finalEffects

Slide 45

Slide 45 text

45 This is also a pattern join :: Monad M => M (M a) -> M a join :: IO (IO a) -> IO a join (handlePizza <> handleNukes)

Slide 46

Slide 46 text

46 No Boilerplate! join :: Monad M => M (M a) -> M a join :: IO (IO a) -> IO a join (handlePizza <> handleNukes)

Slide 47

Slide 47 text

47 Final Code
 handlePizza handlePizza :: IO (IO ()) handlePizza = do write "Do you want a pizza?" canOrder <- read return $ when (canOrder == "Yes") orderPizza

Slide 48

Slide 48 text

48 Final Code
 handleNukes handleNukes :: IO (IO ()) handleNukes = do write “Should I launch nukes?" canLaunch <- read return $ when (canLaunch == "Yes") launchNukes

Slide 49

Slide 49 text

49 Final Code
 Combine flows together join (handlePizza <> handleNukes <> ...) join (mappend [ handlePizza , handleNukes ... ]) Or Perhaps

Slide 50

Slide 50 text

50 ❑We don’t launch nukes without ordering pizza ❑We don’t order pizza when not launching nukes Change Requirements Again

Slide 51

Slide 51 text

51 Must Rearchitect do write "Do you want a pizza?" canOrder <- read write "Should I launch missiles?" canLaunch <- read when (canOrder == “Yes" && canLaunch == "Yes") (orderPizza >> launchMissiles)

Slide 52

Slide 52 text

52 Must Rearchitect do write "Do you want a pizza?" canOrder <- read write "Should I launch missiles?" canLaunch <- read when (canOrder == “Yes" && canLaunch == "Yes") (orderPizza >> launchMissiles) Business Logic

Slide 53

Slide 53 text

53 A General Pattern do write “Question 1 ...” answer1 <- read ... when (validates answer1 ...) performAllEffects

Slide 54

Slide 54 text

54 We Need ❑Define complex flows with user input and a final effect to be performed ❑To compose these flows without boilerplate ❑Call a function on all the user input to determine if we should perform the final effects. ❑Be able to run the final effects together at the end of all user input

Slide 55

Slide 55 text

55 Can we do this with Monoids? do finalEffects <- handlePizza <> handleNukes finalEffects ❑We abstracted away the captured variables ❑Now all we can do is run the final composed effect We can’t access `canOrder` or `canLaunch` here

Slide 56

Slide 56 text

56 FP Gives you Granularly Powerful Abstractions ❑Monads are too powerful (i.e. boilerplate) ❑Monoids abstract away too much ❑Need something in the middle

Slide 57

Slide 57 text

57 Let's work through this data Ret a = Ret { input :: a , effect :: IO () } ❑Return the final effect, AND the user input ❑Parameterise User Input as `a`

Slide 58

Slide 58 text

58 Let's work through this handlePizza :: IO (Ret Boolean) handlePizza = do write "Do you want a pizza?" canOrder <- read return $ Ret canOrder $ when (canOrder == "Yes") orderPizza

Slide 59

Slide 59 text

59 Compose Effects do retPizza <- handlePizza retNuke <- handleNuke when valid (input retPizza) (input retNuke) do effect retPizza effect retNuke

Slide 60

Slide 60 text

60 Compose Effects do retPizza <- handlePizza retNuke <- handleNuke when valid (input retPizza) (input retNuke) do effect retPizza effect retNuke UGH! Boilerplate!

Slide 61

Slide 61 text

61 Compose Effects do retPizza <- handlePizza retNuke <- handleNuke let go = valid (input retPizza) (input retNuke) when go do effect retPizza effect retNuke

Slide 62

Slide 62 text

62 Compose Effects do retPizza <- handlePizza retNuke <- handleNuke let go = valid (input retPizza) (input retNuke) when go do effect retPizza effect retNuke Applicative!

Slide 63

Slide 63 text

63 IO is an Applicative instance Applicative IO where f <*> a = do f' <- f a' <- a pure (f' a')

Slide 64

Slide 64 text

64 Try to Use Applicative IO do go <- valid <$> (input <$> handlePizza) <*> (input <$> handleNuke) when go do effect ??retPizza effect ??retNuke

Slide 65

Slide 65 text

65 Dial Back a Little do (retPizza, retNuke) <- (,) <$> handlePizza <*> handleNuke let go = valid <$> input retPizza <*> input retNuke when go do effect retPizza effect retNuke

Slide 66

Slide 66 text

66 Perhaps a try a different abstraction do (retPizza, retNuke) <- (,) <$> handlePizza <*> handleNuke let go = valid <$> input retPizza <*> input retNuke when go do effect retPizza effect retNuke This is a common pattern Can we abstract this?

Slide 67

Slide 67 text

67 Running a Return value data Ret a = Ret { input :: a , effect :: IO ()} runRet :: Ret Bool -> IO () runRet (Ret b e) = when b e

Slide 68

Slide 68 text

68 More trouble than its worth? do (retPizza, retNuke) <- (,) <$> handlePizza <*> handleNuke let go = valid <$> input retPizza <*> input retNuke runRet ??? We need to Compose a Ret To be able to run it

Slide 69

Slide 69 text

69 However! do (retPizza, retNuke) <- (,) <$> handlePizza <*> handleNuke let go = valid <$> input retPizza <*> input retNuke runRet ??? This could return a Ret instead!

Slide 70

Slide 70 text

70 Combining Return values data Ret a = Ret { input :: a , effect :: IO ()} instance Functor Ret where fmap f (Ret a e) = Ret (f a) e instance Applicative Ret where Ret f e1 <*> Ret a e2 = Ret (f a) (e1 <> e2)

Slide 71

Slide 71 text

71 Less Boilerplate! do (retPizza, retNuke) <- (,) <$> handlePizza <*> handleNuke let ret = valid <$> retPizza <*> retNuke runRet ret

Slide 72

Slide 72 text

72 Hmm, Still Boilerplatey do (retPizza, retNuke) <- (,) <$> handlePizza <*> handleNuke let ret = valid <$> retPizza <*> retNuke runRet ret Two Successive Applicatives

Slide 73

Slide 73 text

73 Hmm, Still Boilerplatey do (retPizza, retNuke) <- (,) <$> handlePizza <*> handleNuke let ret = valid <$> retPizza <*> retNuke runRet ret Combine Effectful
 IO Combine Effectful
 Ret

Slide 74

Slide 74 text

74 Compose Applicatives? data IO a = ... data Ret a = Ret { input :: a , effect :: IO ()} type Flow a = IO (Ret a) We need an Applicative instance for Flow

Slide 75

Slide 75 text

75 Applicatives Compose! Import Data.Functor.Compose type Compose f g a = Compose (f (g a)) type Flow a = Compose IO Ret a

Slide 76

Slide 76 text

76 Applicatives Compose! instance (Applicative f, Applicative g) => Applicative (Compose f g) where Compose f <*> Compose x = Compose (liftA2 (<*>) f x)

Slide 77

Slide 77 text

77 Running Compose runRet :: Ret Bool -> IO () runRet (Ret b e) = when b e runFlow :: Compose IO Ret Bool -> IO () runFlow (Compose e) = e >>= runRet

Slide 78

Slide 78 text

78 Defining Flows handlePizza :: Flow Boolean handlePizza = Compose $ do write "Do you want a pizza?" canOrder <- read return $ Ret canOrder $ when (canOrder == "Yes") orderPizza

Slide 79

Slide 79 text

79 Composing Flow With Business Logic valid <$> handlePizza <*> handleNukes <*> ...

Slide 80

Slide 80 text

80 No Boilerplate runFlow $ valid <$> handlePizza <*> handleNuke

Slide 81

Slide 81 text

81 ❑Type safety. Eliminates a large class of errors. ❑Effectful values are first class ❑Higher Order Patterns ❑Reduction in Boilerplate ❑Zero Cost Code Reuse Takeaways

Slide 82

Slide 82 text

82 Side Effects !!

Slide 83

Slide 83 text

83 Besties !!

Slide 84

Slide 84 text

84 Thank You Questions?