Search code examples
haskellcompositiondo-notation

Function Composition Do Notation


Is there a "do notation" syntactic sugar for simple function composition?

(i.e. (.) :: (b -> c) -> (a -> b) -> a -> c)

I'd like to be able to store results of some compositions for later (while still continuing the chain.

I'd rather not use the RebindableSyntax extension if possible.

I'm looking for something like this:

composed :: [String] -> [String]
composed = do
    fmap (++ "!!!")
    maxLength <- maximum . fmap length
    filter ((== maxLength) . length)

composed ["alice", "bob", "david"]
-- outputs: ["alice!!!", "david!!!"]

I'm not sure something like this is possible, since the result of the earlier function essentially has to pass "through" the bind of maxLength, but I'm open to hearing of any other similarly expressive options. Basically I need to collect information as I go through the composition in order to use it later.

Perhaps I could do something like this with a state monad?

Thanks for your help!

Edit

This sort of thing kinda works:

split :: (a -> b) -> (b -> a -> c) -> a -> c
split ab bac a = bac (ab a) a

composed :: [String] -> [String]
composed = do
    fmap (++ "!!!")
    split 
        (maximum . fmap length)
        (\maxLength -> (filter ((== maxLength) . length)))

Solution

  • As leftaroundabout mentioned, you can use Arrows to write your function. But, there is a feature in ghc Haskell compiler, which is proc-notation for Arrows. It is very similar to well-known do-notation, but, unfortunately, not many people aware of it.

    With proc-notation you can write your desired function in next more redable and elegant way:

    {-# LANGUAGE Arrows #-}
    
    import Control.Arrow (returnA)
    import Data.List     (maximum)
    
    composed :: [String] -> [String]
    composed = proc l -> do
        bangedL <- fmap (++"!!!")        -< l
        maxLen  <- maximum . fmap length -< bangedL
        returnA -< filter ((== maxLen) . length) bangedL
    

    And this works in ghci as expected:

    ghci> composed ["alice", "bob", "david"]
    ["alice!!!","david!!!"]
    

    If you are interested, you can read some tutorials with nice pictures to understand what is arrow and how this powerful feature works so you can dive deeper into it:

    https://www.haskell.org/arrows/index.html

    https://en.wikibooks.org/wiki/Haskell/Understanding_arrows