I’m writing this down before I forget because it’s really cool. Imagine you have the following code
> g a b = (show a) ++ (show b)
> h = (+5)
> f x = g (h x) x
And you run
pointfree, because you enjoy being made to look stupid. It’ll print up
> f' = g =<< h
You’ll stare at this going “I didn’t even say anything here was a Monad, but OK” try typing into your GHCi and… GHCi will tell you it doesn’t type check. Suitably chastened, you’ll go off to learn more Haskell elsewhere.
About a year later, you revist the problem and remember that all functions are examples of Reader monads and things start to make sense. You try
> import qualified Control.Monad.Reader
And, hey presto, it actually works. You can even check on the command line that it works. Let’s talk about how.
Start with a nice simple function
x :: initial -> intermediate. As long as we’ve got
Control.Monad.Reader imported, it’s automatically an
m intermediate, where
m means “a function taking an
Since it’s a monadic value, we can reasonably ask what
(y =<< x) is. Well,
y has got to be a function that is of the form
b -> m result. Since
m in this case is “a function taking an
input”, that makes
intermediate -> input -> result. So the whole thing becomes
input -> result.
This finally explains to me why so much of the
lens library is written in terms of
(MonadReader s m): it provides an extra free level of generality as long as you recall that
(->) s (which is a function that takes an
s) satisfies it. i.e. you can just read it as
m b as
s -> b.
I don’t think I’ve ever published a FizzBuzz solution on the blog, so here’s one that heavily uses this reader monad trick.
> import Data.Maybe (Maybe(Just, Nothing), fromMaybe) > import Data.Foldable (asum, for_)
> import qualified GHC.Base > -- also provides the MonadReader instance
> toMaybe :: Bool -> a -> Maybe a > toMaybe False _ = Nothing > toMaybe _ value = Just value
> fizzbuzz :: Integer -> String > fizzbuzz = fromMaybe <$> show <*> asum . sequence rules > where fb m output n = toMaybe (n `mod` m == 0) output > rules = [fb 15 "FizzBuzz", fb 3 "Fizz", fb 5 "Buzz"]
> main :: IO () > main = for_ [1..100] $ putStrLn . fizzbuzz
Code golfers welcome.