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.