SHARE
TWEET

Super quick monad uses

kronicmage Mar 12th, 2020 (edited) 190 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import Control.Monad
  2.  
  3. -- I recently wrote a bunch of code at work today that was a relatively common real worl use
  4. -- of monads. Thought I'd share
  5.  
  6. -- Suppose you want to do the following:
  7. -- 1. Take an integer as input
  8. -- 2. Verify that it's a certain form (i.e. a multiple of 7 maybe) (in real world, this was verifying a checksum)
  9. -- 3. Check that a corresponding element exists in a database
  10. -- 4. Verify that the value attached to that element in the database is also a valid form
  11. --
  12. -- In something like python, you would probably do something like this all day:
  13. -- > def f(x):
  14. -- >     if valid_input(x):
  15. -- >          if x in database:
  16. -- >               value = database[x]
  17. -- >               if valid_output(value):
  18. -- >                    return value
  19. -- >     raise SomeException
  20. --
  21. -- You can probably see that as checks and conditions and complexity grows with this kind of thing,
  22. -- it can quickly get unwieldy. The natural response for this in langauges like Python, of course,
  23. -- is to ask for forgiveness rather than permission: just do the computations and catch exceptions later.
  24. --
  25. -- The problem with that approach, especially with larger code bases, and multiple engineers working
  26. -- on the same codebase, is that it becomes increasingly difficult to tell when something will throw an
  27. -- error, which errors will be thrown, when to catch errors (are these errors already handled lower/higher
  28. -- on the stack?), and in particular, it's almost impossible to guarantee that your code will not crash.
  29. --
  30. -- In Haskell, we are a statically typed language. Everything that a function does should have a
  31. -- type signature that corresponds to what to expect.
  32. -- Say you have a constant integer in haskell. What would that look like?
  33. -- > x :: Int
  34. -- > x = 5
  35. -- In Java, for comparison, that would be
  36. -- > int x = 5;
  37. -- But in Java, what about an integer that you read from stdin? It would also just have a type signature of `int`.
  38. -- Haskell is special in that since `x :: Int` looks like a constant, you would need some special
  39. -- type to denote that x is not a constant, for instance, `x :: IO Int`
  40. --
  41. -- Back to our earlier example: in Haskell, throwing exceptions willy nilly is bad practice.
  42. -- You want your type signatures to represent something that is failable. Because ultimately, with
  43. -- a properly constructed type definition, then successful compilation => never crashing!!!
  44.  
  45. -- So for our example monad, we have a data type that can either hold the successful result
  46. -- of a computation, or an object describing an error that has occured.
  47. data Exceptable a = Error String | Result a deriving (Show)
  48.  
  49. -- For this to be a useful exception type, it should be something that essentially
  50. -- allows you to run functions on it as normal if we got a valid result.
  51. -- But when there's an error, it should carry that error through to the end.
  52. instance Monad Exceptable where
  53.     return = Result
  54.     (Error  x) >>= f = Error x
  55.     (Result y) >>= f = f y
  56.  
  57. -- for compatibilty reasons, I also need to define the Applicative and Functor instance
  58. -- I won't explain those for now, but you can look it up for interest.
  59.  
  60. instance Applicative Exceptable where
  61.     pure  = return
  62.     (<*>) = ap
  63. instance Functor Exceptable where fmap f x = x >>= (return . f)
  64.  
  65. -- Now, back to monads.
  66. -- The core of monadic programming style, are functions of the form `(a -> m b)`
  67. -- The secret is that monads make it extremely easy to chain and sequence
  68. -- functions of that form together. So it is extremely easy for separate developers
  69. -- to build smaller building blocks, and then compose our way up to bigger building blocks
  70. -- For instance, we can turn each of our 4 steps into a separate (a -> m b) function,
  71. -- and then chain it all together
  72. -- (note: I'm gonna write really contrived versions of what actually happened just for the sake of
  73. -- simplicity and time)
  74.  
  75. -- In realworld, this would have been a proper input checksum check lol
  76. validate_input :: Int -> Exceptable Int
  77. validate_input x = if x `mod` 7 == 0 then Result x else Error "Bad input"
  78.  
  79. -- this was verifying that the entry reference format was valid in the real world
  80. -- so if it was valid, it would return it as a result. If it was invalid, throw error
  81. -- (in real world we have some higher level cleaner ways of doing this kind of check on booleans,
  82. -- but the real world version wasn't a simple boolean check anyway so this is just for simplicity)
  83. valid_reference :: Int -> Exceptable Int
  84. valid_reference 14 = Result 14
  85. valid_reference 21 = Result 21
  86. valid_reference x  = Error "Bad database reference"
  87.  
  88. -- pretend that this was something that read from database
  89. retrieve_from_database :: Int -> Exceptable String
  90. retrieve_from_database 14 = Result "data!"
  91. retrieve_from_database x  = Error "Database permissions error"
  92.  
  93. -- so now, with these (a -> m b) functions in hand, it's time to chain them!
  94. -- suppose we got ourselves some input data that came from elsewhere in the program:
  95.  
  96. input :: Exceptable Int
  97. input = Result 5
  98.  
  99. -- then we can bind it to all of our smaller components to get what we want!
  100. output :: Exceptable String
  101. output = input >>= validate_input >>= valid_reference >>= retrieve_from_database
  102. -- > Error "Bad input"
  103.  
  104. -- so we were able to do what the big if statement chain did in python, but
  105. -- in practically one line!
  106. -- In order to achieve the same level of run time safety in Python, you would need all sorts
  107. -- of nesting, or stack management shenanigans. Lots of layers.
  108. -- The joy of monads is that they are able to keep everything flat!
  109. -- The input to (>>=) is (m a). The output of (>>=) is a (m b). Nowhere do we have (m (m (m a))) or whatever
  110. -- By keeping things flat, and making outputs of >>= the same as the input of bind, it becomes possible
  111. -- to chain lots of smaller components together in one go
  112. -- Composability is key!
  113.  
  114. -- For instance, we could have combined input sanitation and format validation in one go:
  115. validate_input_and_format :: Int -> Exceptable Int
  116. validate_input_and_format = validate_input >=> valid_reference
  117. -- (>=>) is (.) but for functions (a -> m b) instead of (a -> b)
  118. -- f >=> g = \x -> f x >>= g
  119. -- So our output computation can be a bit smaller:
  120. output2 :: Exceptable String
  121. output2 = input >>= validate_input_and_format >>= retrieve_from_database
  122.  
  123. -- or, we could just make output a function directly! like so...
  124. output_func :: Int -> Exceptable String -- note the (a -> m b) form!
  125. output_func input = validate_input input >>= valid_reference >>= retrieve_from_database
  126. -- ...which is equivalent to...
  127. output_func2 :: Int -> Exceptable String
  128. output_func2 = validate_input >=> valid_reference >=> retrieve_from_database
  129.  
  130. -- so now we can just run:
  131. -- > output_func2 7
  132. -- -> Error "Bad database reference"
  133. -- > output_func2 14
  134. -- -> Result "data!"
  135.  
  136. -- So as we get non-error data from other parts of the program, we can just >>= that data into output_func
  137. -- and get more data. Composability of effectful actions is the core idea of monads.
  138.  
  139. -- I ask you again to consider what the python equivalent of this would be.
  140. -- To get this kind of simplicity, flatness, and composability in Python, you would need
  141. -- a lot of exception throwing and catching (since if statement chains can't get you this
  142. -- level of flatness). And that just gets messy in large scale.
  143. -- Sure, if you were to make something similar to this super small program in that manner,
  144. -- then you would be fine. But with large code bases, that quickly breaks down.
  145. -- Monads let you keep things clean and flat, no matter how large your code gets
  146.  
  147. -- There's a lot more I can talk about monads. There is another reason that they're used -
  148. -- they have a strong theoretical background from category theory. A lot of higher level
  149. -- control structures of monads are lifted straight from once-theoretical category theory
  150. -- constructs. By using the math so directly, we benefit from all the theorems and results
  151. -- that they provide.
  152.  
  153. -- A lot more things are monads than one may realize. For example, lists are monads:
  154. -- > instance Monad [] where
  155. -- >     return x = [x]
  156. -- >     lst >>= f = concatMap f lst
  157. -- (note: concatMap is just flatMap (a.k.a. map function, then flatten list))
  158. -- And list comprehensions are just monad structures!
  159.  
  160. -- for example, a basic comprehension:
  161. comprehension_a = [(x, y) | x <- [3, 4, 5], y <- [1, 2, 3], x + y == 6]
  162. -- has the following monadic form:
  163. comprehension_b = [3, 4, 5] >>= \x -> [1, 2, 3] >>= \y -> guard (x + y == 6) >> return (x, y)
  164. -- in haskell, we have syntax sugar for this called do-notation:
  165. comprehension_c = do
  166.     x <- [3, 4, 5]
  167.     y <- [1, 2, 3]
  168.     guard (x + y == 6)
  169.     return (x, y)
  170. -- where ([letter] <- [val]) is sugar for [val] >>= \[letter] -> ...
  171. -- and every other line has implicit `>>` between them
  172.  
  173. -- another surprising one: functions are monads! If you know continuation passing style,
  174. -- then I can tell you that CPS is equivalent to the monad instance of functions. In pseudo-haskell,
  175. -- > instance Monad (r ->) where
  176. -- >     return x = \_ -> x
  177. -- >     f >>= g  = \r -> g (f r) r
  178. -- Why is this the implementation you may ask? Because this is the only implementation that follows
  179. -- the monad laws from math. That's beyond the scope of this right now though
  180.  
  181. -- So since so many things are monads, that lets us write really general code as control flow structures!
  182. -- For example:
  183. generalized_cartesian :: Monad m => m a -> m b -> m (a, b)
  184. generalized_cartesian xs ys = do
  185.     x <- xs
  186.     y <- ys
  187.     return (x, y)
  188.  
  189. -- > generalized_cartesian [1, 2] [3, 4]
  190. -- -> [(1, 3), (1, 4), (2, 3), (2, 4)]
  191. -- > generalized_cartesian (Result 3) (Result 4)
  192. -- -> Result (3, 4)
  193. -- And of course, the function monad :)
  194. -- > generalized_cartesian (\x -> x*x) (\x -> x+x) $ 5
  195. -- -> (25, 10)
  196.  
  197. -- In general, monads let you structure a potentially stateful computation,
  198. -- by letting you separate the context/structure of a computation from its execution
  199. -- In principle, its possible to write a monad such that the following do block is valid code
  200. -- and behaves like an assembly language:
  201. -- > do
  202. -- >     add 3 0 3
  203. -- >     mov 3 4
  204. -- >     label "asdf"
  205. -- >     jlz 3 "asdf"
  206. -- Implementation is an exercise for the reader :).
  207.  
  208. -- So long as every monad instance follows the monad laws, then we achieve ultimate control
  209. -- over state, effects, and the order in which things are run. We can do so in such a manner
  210. -- that avoids aribtrary control flow (who knows where you'll end up when you throw an exception???),
  211. -- that is statically typed (you can tell when something can error/change state just from the type!!!),
  212. -- and that can be always flat and doesn't need to nest (unlike big if-chains!)
  213. -- Furthermore, the same mathematical structure that allows you to do this has applications in many
  214. -- more areas that allow us to write really really general combinators that can sequence computations of
  215. -- any kind, as long as they are monads!
  216.  
  217. -- Sorry if this doesn't make sense it's kinda late when I'm writing this lmao
  218. -- But that's the gist of why monads are nice
  219. -- Ask me if you have any questions
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top