Home > Back-end >  Currying with Haskell with functor and function composition
Currying with Haskell with functor and function composition

Time:02-18

I have following problem. I have a function that works nice.

getName :: Style -> Css -> Maybe String
getName s css = map intToChar .  intToBase 26 <$> getIndex s css

I wonder why I can't define it that way:

getName :: Style -> Css -> Maybe String
getName = map intToChar .  intToBase 26 <$> getIndex

I thought currying would allow me to return that as function that will take two parameters instead of being a function that have two parameters

If it helps your for understanding:

intToChar :: Int -> Char
intToBase :: Int -> Int -> [Int]
getIndex :: Style > Css -> Maybe Int

Ahh don't forget the error:

error:
    • Couldn't match type ‘[a] -> Maybe Int’ with ‘Int’
      Expected type: a -> Int
        Actual type: a -> [a] -> Maybe Int
    • Probable cause: ‘getIndex’ is applied to too few arguments
      In the second argument of ‘(<$>)’, namely ‘getIndex’
      In the expression: map intToChar . intToBase 26 <$> getIndex
      In an equation for ‘getName’:
          getName = map intToChar . intToBase 26 <$> getIndex
    • Relevant bindings include
        getName :: a -> [Char] (bound at src/Css/Css.hs:17:1)
   |        
17 | getName = map intToChar .  intToBase 26 <$> getIndex 
   |                                             ^^^^^^^^

CodePudding user response:

Perhaps some parenthesization is in order. First, the principle you're trying to apply is valid. Namely, if a variable is the final argument to your function and is the final argument to the top-level call in your function, you can effectively "right-cancel" the two variables, so if you had

prependMy x = mappend "my" x

Then you could safely "cancel" the x and get the equivalent (modulo monomorphism restriction, which doesn't apply here but can bite you sometimes)

prependMy = mappend "my"

Now, this is your expression.

getName s css = map intToChar .  intToBase 26 <$> getIndex s css

Now the same thing with some parentheses for clarity

getName s css = (map intToChar .  intToBase 26) <$> (getIndex s css)

Now the css isn't on the top-level anymore. It wasn't before, but it's more clear now that it's not. If you can rearrange the expression so that s and css are actually at the top-level call, then you can still cancel off. In this case, you could get rid of css by doing something like this.

getName s css = (map intToChar .  intToBase 26) <$> (getIndex s css)
getName s css = ((map intToChar .  intToBase 26) <$>) (getIndex s css)
getName s css = (((map intToChar .  intToBase 26) <$>) . getIndex s) css
getName s = ((map intToChar .  intToBase 26) <$>) . getIndex s

Then you can get rid of s by following the same logic

getName s = ((map intToChar .  intToBase 26) <$>) . (getIndex s)
getName s = (((map intToChar .  intToBase 26) <$>) .) (getIndex s)
getName s = ((((map intToChar .  intToBase 26) <$>) .) . getIndex) s
getName = (((map intToChar .  intToBase 26) <$>) .) . getIndex

But that's not really more readable than the original. Removing the arguments like this is called pointfreeing a function. It can be done to basically any function, but it should be done sparingly, as you can easily end up with something incomprehensible.

CodePudding user response:

The problem is that s and css cannot be eliminated via eta conversion, because they are used as arguments to getIndex alone, not map intToChar . intToBase 26 <$> getIndex.

To go point-free, you have to use function composition. To start, rewrite your definition using fmap instead of <$> (and hiding the irrelevant argument to fmap behind a local variable).

getName s css = fmap f (getIndex s css)
    where f = map intToChar . intToBase 26

Now it's easier to see that you need to use function composition to first eliminate css:

-- f1 (f2 x) == f1 . f2
-- f1 = fmap f
-- f2 = getIndex s
getName s = fmap f . (getIndex s)
    where f = map intToChar . intToBase 26

and then s:

-- f1 (f2 x) = f1 . f2
-- f1 = (fmap f .)
-- f2 = getIndex
getName = (fmap f . ) . getIndex
    where f = map intToChar . intToBase 26

CodePudding user response:

First let's eta-reduce one argument. This requires transforming the operator into a section

getName s css = map intToChar . intToBase 26 <$> getIndex s css

getName s = (map intToChar . intToBase 26 <$>) . getIndex s

aka

getName s = fmap (map intToChar . intToBase 26) . getIndex s

Now, it is possible to see function post-composition as another functor operation, but it would need to be mentioned explicitly:

getName s = fmap (fmap (map intToChar . intToBase 26)) (getIndex s)

This is awkward. Some libraries define operators like <<$>> that allow compacting this to

getName s = map intToChar . intToBase 26 <<$>> getIndex s

but this doesn't really scale well – you'd need a different operator for every depth of nesting. Though, we could certainly define it ourselves:

infixl 4 <<<$>>>
(<<<$>>>) :: (Functor f, Functor g, Functor h)
            => (a->b) -> f (g (h a)) -> f (g (h b))
(<<<$>>>) = fmap . fmap . fmap

getName = map intToChar . intToBase 26 <<<$>>> getIndex

Alternatively you could merge, i.e. compose the different functors into a single one:

getName s = getCompose $ map intToChar . intToBase 26 <$> Compose (getIndex s)

...which kind-of scales also to including the other argument

getName = getCompose . getCompose $ map intToChar . intToBase 26 <$> Compose (Compose getIndex)

but that's obviously no win at all over any of the previous forms. The same thing could also be expressed with the ReaderT monad transformer, but that gets just as unreadable. Likewise for various point-free escapades involving partially applied composition operators.

tl;dr it's not worth it, just keep the arguments as you had them originally.


I'm generally not a fan of the (c->) functor. Haskell has enough functors floating around, but it has excellent special syntax for plain old functions, so why not just use it and keep it clear what functor is just a function composition.

  • Related