How do I re-write a Haskell function of two argume

2019-01-21 20:29发布

问题:

I have the following function in Haskell

agreeLen :: (Eq a) => [a] -> [a] -> Int
agreeLen x y = length $ takeWhile (\(a,b) -> a == b)  (zip x y)

I'm trying to learn how to write 'idiomatic' Haskell, which seem to prefer using . and $ instead of parenthesis, and also to prefer pointfree code where possible. I just can't seem to get rid of mentioning x and y explicitly. Any ideas?

I think I'd have the same issue with pointfreeing any function of two arguments.

BTW, this is just in pursuit of writing good code; not some "use whatever it takes to make it pointfree" homework exercise.

Thanks.


(Added comment) Thanks for the answers. You've convinced me this function doesn't benefit from pointfree. And you've also given me some great examples for practicing transforming expressions. It's still difficult for me, and they seem to be as essential to Haskell as pointers are to C.

回答1:

and also to prefer pointfree code where possible.

Not "where possible", but "where it improves readability (or has other manifest advantages)".

To point-free your

agreeLen x y = length $ takeWhile (\(a,b) -> a == b)  (zip x y)

A first step would be to move the ($) right, and replace the one you have with a (.):

agreeLen x y = length . takeWhile (\(a,b) -> a == b) $ zip x y

Now, you can move it even further right:

agreeLen x y = length . takeWhile (uncurry (==)) . zip x $ y

and there you can immediately chop off one argument,

agreeLen x = length . takeWhile (uncurry (==)) . zip x

Then you can rewrite that as a prefix application of the composition operator,

agreeLen x = (.) (length . takeWhile (uncurry (==))) (zip x)

and you can write

f (g x)

as

f . g $ x

generally, here with

f = (.) (length . takeWhile (uncurry (==)))

and g = zip, giving

agreeLen x = ((.) (length . takeWhile (uncurry (==)))) . zip $ x

from which the argument x is easily removed. Then you can transform the prefix application of (.) into a section and get

agreeLen = ((length . takeWhile (uncurry (==))) .) . zip

But, that is less readable than the original, so I don't recommend doing that except for practicing the transformation of expressions into point-free style.



回答2:

You could also use:

agreeLen :: (Eq a) => [a] -> [a] -> Int
agreeLen x y = length $ takeWhile id $ zipWith (==) x y

Idiomatic Haskell is whatever is easier to read, not necessarily what is most point-free.



回答3:

As pointed out in Daniel's excellent answer, your problem is to compose f and g when f as one argument and g two. this could be written f ??? g with the correct operator (and with a type signature of (c -> d) -> (a -> b -> c) -> a -> b -> d. This correspond to the (.).(.) operator (see there) which is sometimes defines as .:. In that case your expression becomes

length . takeWhile (uncurry (==)) .: zip

If you are used to the .: operator, then this point free version is perfectly readable. I can also instead use (<$$$>) = fmap fmap fmap and get

length . takeWhile (uncurry (==)) <$$$> zip


回答4:

Another concise, point-free solution:

agreeLen = ((length . takeWhile id) .) . zipWith (==)

Equivalently:

agreeLen = (.) (length . takeWhile id) . zipWith (==)