Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Mocking (OsloSFP edition)

Functional Mocking (OsloSFP edition)

Mocking is an infamous technique from object-oriented programming. The goal is to be able to test stateful systems in small pieces by simulating the behaviour of certain objects. The problem with mocking is that it usually requires heavyweight frameworks and clutters test code. There are countless rants on that topic, but this talk isn't one. Instead, we'll explore the functional approach in Haskell: Designing a small language supporting the desired behaviour, and then writing interpreters which can execute its semantics in various ways. Testing I/O code was never easier.

Demo code: https://gist.github.com/larsrh/5cd5652c25ec84b8852c

A1216674d5c9747bcdcc716872439137?s=128

Lars Hupel

June 23, 2015
Tweet

Transcript

  1. Functional Mocking Lars Hupel June 23rd, 2015

  2. What even is “Mocking”? “ In object-oriented programming, mock objects

    are simulated objects that mimic the behavior of real objects in controlled ways. Wikipedia: Mock object ” 2
  3. What even is “Mocking”? “ In object-oriented programming, mock objects

    are simulated objects that mimic the behavior of real objects in controlled ways. Wikipedia: Mock object ” 2
  4. Mocking in Scala Example: ScalaMock def testTurtle { val m

    = mock[Turtle] (m.setPosition _).expects(10.0, 10.0) (m.forward _).expects(5.0) (m.getPosition _).expects().returning(15.0, 10.0) drawLine(m, (10.0, 10.0), (15.0, 10.0)) } 3
  5. Mocking: Why Not? “ When you write a mockist test,

    you are testing the outbound calls of the SUT to ensure it talks properly to its suppliers ... Mockist tests are thus more coupled to the implementation of a method. Changing the nature of calls to collaborators usually cause a mockist test to break. Martin Fowler ” 4
  6. Mocking: Why Not? “ When you write a mockist test,

    you are testing the outbound calls of the SUT to ensure it talks properly to its suppliers ... Mockist tests are thus more coupled to the implementation of a method. Changing the nature of calls to collaborators usually cause a mockist test to break. Martin Fowler ” 4
  7. It is pitch black. You are likely to be eaten

    by a grue.
  8. Functional Mocking ... there’s no such thing. 6

  9. Functional Mocking ... there’s no such thing. various techniques avoid

    the need for mocking altogether 6
  10. Functional Programming ▶ separation of data and operations ▶ parametric

    polymorphism ▶ higher-order functions ▶ lightweight interpreters 7
  11. A Simple Calculator data Expr = Literal Int | Var

    String | Sum Expr Expr evaluate :: Map String Int -> Expr -> Maybe Int 8
  12. None
  13. A Simple Calculator data Expr = Literal Int | Var

    String | Sum Expr Expr evaluate :: (String -> Maybe Int) -> Expr -> Maybe Int 10
  14. A Simple Calculator data Expr a = Literal a |

    Var String | Sum (Expr a) (Expr a) evaluate :: Num a => (String -> Maybe a) -> Expr a -> Maybe a 10
  15. A (Not So) Simple Calculator data Expr a t =

    Literal a | Var t | Sum (Expr a t) (Expr a t) evaluate :: Num a => (t -> Maybe a) -> Expr a t -> Maybe a 10
  16. A (Not So) Simple Calculator data Expr a t =

    Literal a | Var t | Sum (Expr a t) (Expr a t) evaluateM :: (Num a, Monad m) => (t -> m a) -> Expr a t -> m a 10
  17. None
  18. Why This Complexity? “ Always implement things when you actually

    need them, never when you just foresee that you need them. Ron Jeffries about YAGNI ” 12
  19. What Have We Gained? Abstraction over Num ▶ no messing

    around with the values ▶ caller knows that only the Num influences the behaviour Abstraction over Monad ▶ uniform data access ▶ Map ▶ database lookup ▶ reading from standard input 13
  20. Even More Abstraction -- get all variables vars :: Expr

    a t -> [t] vars = error ”some traversal” -- check definedness of all variables check :: Expr a (Maybe t) -> Maybe (Expr a t) check = error ”traversing again?!” -- substitute variables subst :: (t -> Expr a t) -> Expr a t -> Expr a t subst = error ”seriously?” 14
  21. Even More Abstraction -- get all variables vars :: Expr

    a t -> [t] vars = Data.Foldable.toList -- check definedness of all variables check :: Expr a (Maybe t) -> Maybe (Expr a t) check = Data.Traversable.sequenceA -- substitute variables subst :: (t -> Expr a t) -> Expr a t -> Expr a t subst = (=<<) 14
  22. None
  23. Aspect-Oriented Programming What if we want to log the variable

    access in evaluateM? 16
  24. None
  25. YAGNI Revisited ▶ without early abstraction, many concepts stay hidden

    ▶ YAGNI limits thinking ▶ especially important when building libraries 18
  26. Interlude: Immutable Data Structures class Person { private String name;

    public String getName() { return name; } public void setName(String name) { this.name = name; } } 19
  27. Setter and Getter in Java company.getITDepartment() .getHead() .setName(”Grace Hopper”); 20

  28. Immutable Data in Haskell data Company = data Department =

    Company { Department { it :: Department boss :: Person , hr :: Department , budget :: Currency } } data Person = Person { name :: String } 21
  29. Updating Immutable Data company { it = (it company) {

    boss = (boss (it company)) { name = ”Grace Hopper” } } } 22
  30. Updating Immutable Data company { it = (it company) {

    boss = (boss (it company)) { name = ”Grace Hopper” } } } 22
  31. Costate Comonad Coalgebra ...? 23

  32. Lenses To The Rescue! The Naive Formulation data Lens a

    b = Lens { get :: a -> b set :: a -> b -> a } 24
  33. Lenses To The Rescue! The Naive Formulation data Lens a

    b = Lens { get :: a -> b set :: a -> b -> a } The Advanced Formulation type Lens a b = forall f. Functor f => (a -> f a) -> (b -> f b) 24
  34. What Have We Gained? ▶ Composition for free! set (it

    . boss . name) ”Grace Hopper” company ▶ Mocking for free! lenses are ordinary functions, so can be swapped out 25
  35. None
  36. Calculator Revisited data Expr a t = Literal a |

    Var t | Sum (Expr a t) (Expr a t) 27
  37. Calculator Revisited data Expr a t = Literal a |

    Var t | Sum (Expr a t) (Expr a t) > :t Sum Sum :: Expr a t -> Expr a t -> Expr a t 27
  38. Calculator Revisited data Expr a t where Literal :: a

    -> Expr a t Var :: t -> Expr a t Sum :: Expr a t -> Expr a t -> Expr a t 27
  39. Calculator Revisited data Expr a t where Literal :: a

    -> Expr a t Var :: t -> Expr a t Sum :: Expr a t -> Expr a t -> Expr a t ▶ So far: Expr a t contains only a literals ▶ type a is constant in the whole expression ▶ What if we want heterogeneous operations? > :t even even :: Integral a => a -> Bool 27
  40. Calculator Revisited data Expr a where Literal :: a ->

    Expr a Sum :: Expr a -> Expr a -> Expr a 27
  41. Calculator Revisited data Expr a where Literal :: a ->

    Expr a Sum :: Num a => Expr a -> Expr a -> Expr a Even :: Integral a => Expr a -> Expr Bool 27
  42. Calculator Revisited data Expr a where Literal :: a ->

    Expr a Sum :: Num a => Expr a -> Expr a -> Expr a Even :: Integral a => Expr a -> Expr Bool Cast :: (a -> b) -> Expr a -> Expr b 27
  43. A Fancy Calculator ▶ we now have a datatype which

    represents (some) arithmetic operations ▶ Apart from evaluating, what can we do with it? 28
  44. A Fancy Calculator ▶ we now have a datatype which

    represents (some) arithmetic operations ▶ Apart from evaluating, what can we do with it? ▶ print ▶ count operations ▶ optimize 28
  45. A Fancy Calculator ▶ we now have a datatype which

    represents (some) arithmetic operations ▶ Apart from evaluating, what can we do with it? ▶ print ▶ count operations ▶ optimize 28
  46. None
  47. 30

  48. monad (n.) (in the pantheistic philosophy of Giordano Bruno) a

    fundamental metaphysical unit that is spatially extended and psychically aware – Collins English Dictionary
  49. The Essence of IO ▶ a representation of a computation

    ▶ ... which interacts with the world 32
  50. The Essence of IO ▶ a representation of a computation

    ▶ ... which interacts with the world 32
  51. The Essence of IO ▶ a representation of a computation

    ▶ ... which interacts with the world ▶ in Haskell: may contain all sorts of effects ▶ in GHC: opaque, non-inspectable 32
  52. The Essence of IO ▶ a representation of a computation

    ▶ ... which interacts with the world ▶ in Haskell: may contain all sorts of effects ▶ in GHC: opaque, non-inspectable ▶ but: a better world is possible 32
  53. IO as a DSL ▶ calculator: datatype with one constructor

    per operation ▶ terminal application: datatype with one constructor per operation? ▶ read from standard input ▶ write to standard output 33
  54. IO as a DSL ▶ calculator: datatype with one constructor

    per operation ▶ terminal application: datatype with one constructor per operation? ▶ read from standard input ▶ write to standard output ▶ open file ▶ read from file ▶ ... 33
  55. IO as a DSL ▶ calculator: datatype with one constructor

    per operation ▶ terminal application: datatype with one constructor per operation? ▶ read from standard input ▶ write to standard output ▶ open file ▶ read from file ▶ ... 33
  56. A Datatype for Terminal IO data Terminal a where ReadLine

    :: Terminal String WriteLine :: String -> Terminal () 34
  57. A Datatype for Terminal IO data Terminal a where ReadLine

    :: Terminal String WriteLine :: String -> Terminal () ▶ Terminal is an ordinary GADT ▶ nicely represents what we want ▶ But what to do with it? 34
  58. A Datatype for Terminal IO data Terminal a where ReadLine

    :: Terminal String WriteLine :: String -> Terminal () ▶ Terminal is an ordinary GADT ▶ nicely represents what we want ▶ But what to do with it? 34 It looks like you need a monad. Want help with that?
  59. Free Monads Informally: We need a construction which, given any

    type constructor, produces an instance of class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a 35
  60. None
  61. Putting It All Together ▶ Recall our Terminal data type

    ▶ Stick it into FreeM and call it a day! data Terminal a where ReadLine :: Terminal String WriteLine :: String -> Terminal () 37
  62. Putting It All Together ▶ Recall our Terminal data type

    ▶ Stick it into FreeM and call it a day! ▶ ... except, no. data Terminal a where ReadLine :: Terminal String WriteLine :: String -> Terminal () 37
  63. Putting It All Together ▶ Recall our Terminal data type

    ▶ Stick it into FreeM and call it a day! ▶ ... except, no. ▶ It’s not even a Functor data Terminal a where ReadLine :: Terminal String WriteLine :: String -> Terminal () 37
  64. Putting It All Together ▶ Recall our Terminal data type

    ▶ Stick it into FreeM and call it a day! ▶ ... except, no. ▶ It’s not even a Functor data Terminal a where ReadLine :: Terminal String WriteLine :: String -> Terminal () 37 It looks like you need a functor. Want help with that?
  65. Free Functors Informally: We need a construction which, given any

    type constructor, produces an instance of class Functor f where fmap :: (a -> b) -> f a -> f b 38
  66. Coyoneda? “ What is sometimes called the co-Yoneda lemma is

    a basic fact about presheaves (a basic fact of topos theory): it says that every presheaf is a colimit of representables and more precisely that it is the “colimit over itself of all the representables contained in it”. nLab ” 39
  67. Coyoneda? “ What is sometimes called the co-Yoneda lemma is

    a basic fact about presheaves (a basic fact of topos theory): it says that every presheaf is a colimit of representables and more precisely that it is the “colimit over itself of all the representables contained in it”. nLab ” 39
  68. None
  69. None
  70. Simulating IO type IO a = PauseT (State RealWorld) a

    data RealWorld = RealWorld { workDir :: FilePath , files :: Map File Text , isPermitted :: FilePath -> IOMode -> Bool , handles :: Map Handle HandleData , nextHandle :: Integer , user :: User , mvars :: Map Integer MValue , nextMVar :: Integer , writeHooks :: [Handle -> Text -> IO ()] } 42
  71. Conclusion ▶ FP provides a set of techniques for abstraction

    over evaluation ▶ Use them! 43
  72. Conclusion ▶ FP provides a set of techniques for abstraction

    over evaluation ▶ Use them! “ Premature evaluation is the root of all evil. ” 43
  73. Q & A  larsr h  larsrh

  74. Image Credits ▶ Manu Cornet, http://www.bonkersworld.net/building-software/ ▶ Randall Munroe, https://xkcd.com/1312/

    ▶ Thomas Kluyver, Kyle Kelley, Brian E. Granger, https://github.com/ipython/xkcd-font 45