1. 6
  1.  

  2. 6

    There are a lot of “custom monads” which aren’t the free monad. It’s important to stress that you may never actually need the Free Monad. I think also this example unfortunately makes the mistake of showing an example and then hoping you’ll figure out which bits are actually essential to the free monad and which are essential to their example.

    1. 3

      do you have a link to a better example? I still don’t understand the benefits.

      1. 7

        https://michaelxavier.net/posts/2014-04-27-Cool-Idea-Free-Monads-for-Testing-Redis-Calls.html is my favourite example since it comes at it from a practical direction. One aspect is being able to thoroughly decouple interface from implementation - you can enforce that your command language has no direct knowledge of Redis, because the module your commands are defined in doesn’t even have visibility on the Redis-related functions. This becomes more useful when the implementations are not just live and test but multiple storage systems being used in prod.

        But yeah, a lot of of the time it feels like Free is just being used as a lazy way to make a monad, when actually a custom monad would be more appropriate. But if it’s the most concise way to do that then why not?

        1. 2

          The part I wish somebody would write a tutorial on is why Free is often preferable to custom monads for performance reasons. That’s the actual reason it’s needed. :)

          1. 2

            Is Free ever preferable for performance reasons?

            1. 3

              Compared to a non-free (and non-Free) monad, no, it’s no better. With sufficient inlining it may be equivalent. But it’s often better than one-off free monads (meaning monads which are given only by type class and not by concrete type, in function signatures), because cross-module inlining is very finicky and difficult to get right.

              A lot of the details here have changed since the last time I was sufficiently immersed in Haskell to explain them, so I’d have to research. So all I can do is hope somebody else will. :)

    2. 2

      You can make life a little simpler by:

      • using the liftF and iterM functions provided by free
      • adding some typedefs for the composed Free type
      • (here I also made the parameter on IOOp symmetric so you read the same as what you write)
      {-# LANGUAGE DeriveFunctor, LambdaCase #-}
      
      import Control.Monad.Free (Free (..), iterM, liftF)
      
      main :: IO ()
      main = runOp exampleProgram
      
      exampleProgram :: SimpleIOProg ()
      exampleProgram = do
        write "enter 'quit' to exit\n"
        inputLoop
      
      inputLoop :: SimpleIOProg ()
      inputLoop = do
        write "enter some words> "
        text <- inputLn
        if text == "quit" then end
          else do
          let nWords = length $ words text
          write $ "you entered: " ++ show nWords ++ "\n"
          inputLoop
      
      write :: a -> IOProg a ()
      write s = liftF (Write s ())
      
      inputLn :: IOProg a a
      inputLn = liftF (Read id)
      
      end :: IOProg a ()
      end = liftF EndProgram
      
      data IOOp a next
        = EndProgram
        | Read (a -> next)
        | Write a next
        deriving (Functor)
      
      type IOProg a = Free (IOOp a)
      type SimpleIOProg = IOProg String
      
      runOp :: SimpleIOProg () -> IO ()
      runOp = iterM interpret
        where
        interpret = \case
          EndProgram -> return ()
          Read next -> getLine >>= next
          Write s next -> putStr s >> next
      
      1. 1

        As soon as something other than a print statement is imported, you can be pretty sure that monads won’t be explained simply.

        1. 2

          A monad is a type m for which the following functions may be implemented:

          (>>=) :: forall a b. m a -> (a -> m b) -> m b
          return :: a -> m a
          

          Subject to the following rules:

          -- Rule 1
          return a >>= k  =  k a
          -- Rule 2
          m >>= return  =  m
          -- Rule 3
          m >>= (\x -> k x >>= h)  =  (m >>= k) >>= h
          

          That’s it. That is what a monad is (there are equivalent definitions that some people prefer over this one, but all of them give the same result). Monads get some special treatment in Haskell (do notation requires a monad), but other than that there’s nothing magical about them. Monads aren’t magical things. “Monad” is a name for a common pattern: a type whose definition permits the functions defined above.