I'd like to understand more that second point. How is Maybe not morally equivalent to a conditional? If my ifs were in a separate function that would also work, no?
> How is Maybe not morally equivalent to a conditional?
Maybe is a way to represent conditionals that is amenable to higher order patterns. if-then-else conditionals are simply that, whereas Maybe is amenable to Monad, Monoid, and Applicative functor laws. So when we pull out mconcat or <> or mappend, we're actually talking about a higher order pattern.
As I mentioned in the article, we could take that same fizzbuzz code and modify it in many ways under the monoid rules. For example, a different harness in the main function could use Map String Integer and completely change the behavior with the same fizzbuzz function.
I had a bit more time to spare and thought I'd give you an example of this. First, let's rewrite fizzbuzz to totally get rid of any mention Maybe; we'll deal with it outside:
fizzbuzz d i = mconcat (rules <*> pure i) <|> pure (d i)
This is written in terms of an Applicative Functor and a Monoid.
And then can just do some haskell things. I've done less golfing here and more type signatures to make the code more approachable.
{-# LANGUAGE MonadComprehensions, OverlappingInstances, FlexibleInstances#-}
module Main where
import Control.Applicative
import Data.Monoid
import Control.Monad
import Data.Maybe
import Data.List
import qualified Data.HashMap.Strict as M
import System.Environment
-- Let's make it clear what we're working with.
type Counter = M.HashMap String Integer
-- We want an instance slightly different from the default.
instance Monoid Counter where
mempty = M.empty
mappend = M.unionWith (+)
factors = [(3, "fizz"), (5, "buzz"), (7, "bazz")]
-- Our rule function is slightly different.
-- Not unexpected, since our logic has changed. But we could generalize
-- this further!
rules :: [(Integer -> Maybe Counter)]
rules = [\i -> [M.singleton res 1 | i `rem` fac == 0] | (fac,res) <- factors]
-- Fizzbuzz remains unchanged.
fizzbuzz d i = mconcat (rules <*> pure i) <|> pure (d i)
main = do
upTo <- fmap (maybe 100 read . listToMaybe) getArgs
let results = foldl' mappend mempty [ fromJust $ fizzbuzz (const mempty) i | i <- [1..upTo] ]
putStrLn $ show results
And then a typical session:
~/P/h/fb-toys > time ./fbg3 10000000
fromList [("bazz",1428571),("fizz",3333333),("buzz",2000000)]
3.58 real 3.54 user 0.03 sys
Which is a pretty expensive way to avoid doing algebra, but the point is that we're talking about very high level patterns here for fizzbuzz. Fizzbuzz is probably a bad name here, it's more like mergeOptionalPatterns. I'm willing to bet if I dug a round a bit in parser combinator libraries I could find something that does nearly exactly this.
I confess I had to play with it a bit to get it to play nice with large inputs.