如何将一个有两个参数的 Haskell 函数改写为点无关风格

26

我在Haskell中有以下函数

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

我正在尝试学习如何编写“惯用”的Haskell代码,这似乎更喜欢使用 . $ 而不是括号,并且尽可能使用pointfree的代码。但我似乎无法摆脱直接提到xy的习惯。有什么好主意吗?

我认为对于任何两个参数的函数,我都会遇到同样的问题。

顺便说一句,这只是追求编写好代码,而不是什么“使用任何方法使其pointfree”的作业练习。

谢谢。


(附加评论)感谢答案。你们让我相信这个函数不需要pointfree。你们还给了我一些很好的例子来练习转换表达式。这仍然很困难,它们看起来与指针在C中一样重要。


4
我发现 hlint(http://community.haskell.org/~ndm/darcs/hlint/hlint.htm)对我学习使用 . 和 $ 等不同方式书写表达式非常有帮助。 - mhwombat
4个回答

52

尽可能地使用无参函数编码,并在适当的情况下强制使用它。

不是“在适当的情况下”,而是“当它提高可读性(或具有其他明显优势)时”。

要将你的代码转化为无参函数形式:

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

首先,需要将($)移到右边,并用(.)替换掉您现有的符号:

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

现在,您可以将它向右移动得更远:

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

在那里,您可以立即删除一个参数,

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

然后你可以将其重写为复合运算符的前缀应用。

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

你可以把

f (g x)

写成

f . g $ x

一般来说,这里有

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

并且 g = zip,得到

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

从中轻松删除参数x。然后,您可以将前缀应用程序(.)转换为“section”,并获得

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

但是,这比原来的方式更难读,所以我不建议这样做,除非是为了练习将表达式转换为点无风格。


14
值得注意的是,一个名叫Schönfinkel的人在1920年发明了这种技术。 - Tom Crockett

12

你也可以使用:

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

惯用的Haskell是指最易于阅读的代码,而不一定是最point-free的代码。


使用zipWith看起来比我的代码好看多了。但是我认为“易于阅读”的版本与大多数Haskell作者的不同,所以我正在尝试将我的版本更接近他们的风格。 - JonathanZ supports MonicaC
4
@Jonathan 嗯,即使社区对编码风格有不同的看法,但是我可以告诉你我的惯例是:当你使用一个参数的函数链时,我会使用 .,而如果你使用两个或更多参数,就将它们显式表示。至于 $,主要用于消除 do 块末尾挂起的括号,例如 foo $ do ...,或者整理多层嵌套的括号。 - Gabriella Gonzalez

3
正如丹尼尔的出色回答所指出的那样,您的问题是在 fg 中组合 f 作为一个参数和 g 作为两个参数。这可以用正确的运算符写成 f .: g(并带有类型签名 (c -> d) -> (a -> b -> c) -> a -> b -> d)。 这对应于 (.).(.) 运算符(请参见这里),它有时被定义为 .:。在这种情况下,您的表达式变成了
length . takeWhile (uncurry (==)) .: zip

如果您习惯使用.:运算符,那么这个无参版本非常易读。我也可以使用(<$$$>) = fmap fmap fmap代替,并得到
length . takeWhile (uncurry (==)) <$$$> zip

2

另一个简洁的无点解决方案:

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

同义词:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接