Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: