【发布时间】:2015-04-13 00:33:57
【问题描述】:
我正在尝试将以下 Haskell 代码转换为无点样式,但无济于事。
bar f g xs = filter f (map g xs )
我是 Haskell 的新手,任何帮助都会很棒。
【问题讨论】:
-
请注意,在某些情况下(像这样),无点版本的可读性可能比原始代码低得多。除非您的目标是混淆代码,否则请谨慎使用无点样式。
我正在尝试将以下 Haskell 代码转换为无点样式,但无济于事。
bar f g xs = filter f (map g xs )
我是 Haskell 的新手,任何帮助都会很棒。
【问题讨论】:
转换为无点样式可以完全机械地完成,尽管如果不熟悉 Haskell 语法的基本原理(如左关联函数应用程序和 x + y 与 (+) x y 相同)则很难。我假设您对 Haskell 语法感到满意;如果没有,我建议先阅读LYAH 的前几章。
您需要标准库中的以下组合子。我还从组合微积分中给出了它们的标准名称。
id :: a -> a -- I
const :: a -> b -> a -- K
(.) :: (b -> c) -> (a -> b) -> (a -> c) -- B
flip :: (a -> b -> c) -> (b -> a -> c) -- C
(<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c) -- S
一次使用一个参数。将左侧的参数移动到右侧的 lambdas,例如
f x y = Z
变成
f = \x -> \y -> Z
我喜欢一次做一个参数而不是一次做所有参数,这样看起来更干净。
然后根据以下规则消除你刚刚创建的 lambda。我将使用小写字母表示文字变量,大写字母表示更复杂的表达式。
\x -> x,请替换为id
\x -> A,其中A 是没有出现x 的任何表达式,请替换为const A
\x -> A x,其中x 不会出现在A 中,请替换为A。这称为“eta 收缩”。\x -> A B,那么
x 出现在A 和B 中,则替换为(\x -> A) <*> (\x -> B)。x 仅出现在A 中,则替换为flip (\x -> A) B
x仅出现在B中,则替换为A . (\x -> B),x 没有出现在A 或B 中,那么我们应该已经使用了另一个规则。然后向内工作,消除您创建的 lambda。让我们来看看这个例子:
f x y z = foo z (bar x y)
-- Move parameter to lambda:
f x y = \z -> foo z (bar x y)
-- Remember that application is left-associative, so this is the same as
f x y = \z -> (foo z) (bar x y)
-- z appears on the left and not on the right, use flip
f x y = flip (\z -> foo z) (bar x y)
-- Use rule (3)
f x y = flip foo (bar x y)
-- Next parameter
f x = \y -> flip foo (bar x y)
-- Application is left-associative
f x = \y -> (flip foo) (bar x y)
-- y occurs on the right but not the left, use (.)
f x = flip foo . (\y -> bar x y)
-- Use rule 3
f x = flip foo . bar x
-- Next parameter
f = \x -> flip foo . bar x
-- We need to rewrite this operator into normal application style
f = \x -> (.) (flip foo) (bar x)
-- Application is left-associative
f = \x -> ((.) (flip foo)) (bar x)
-- x appears on the right but not the left, use (.)
f = ((.) (flip foo)) . (\x -> bar x)
-- use rule (3)
f = ((.) (flip foo)) . bar
-- Redundant parentheses
f = (.) (flip foo) . bar
你去,现在试试你的!在决定使用哪条规则时,实际上并没有任何聪明之处:使用任何适用的规则,你就会取得进步。
【讨论】:
(flip foo .) . bar)。
现有的两个答案都没有真正以明确的方式回答您的具体问题:一个是“这里是规则,你自己解决”,另一个是“这里是答案,没有关于如何做的信息规则生成它。”
前三个步骤非常简单,包括通过编写h = f . g 从h x = f (g x) 形式的内容中删除常见的x。本质上它是在说“如果你可以以a $ b $ c $ ... $ y $ z 的形式写东西并且你想删除z,把所有的美元都改成点,a . b . c . ... . y:
bar f g xs = filter f (map g xs)
= filter f $ (map g xs)
= filter f $ map g $ xs -- because a $ b $ c == a $ (b $ c).
bar f g = filter f . map g
= (filter f .) (map g)
= (filter f .) $ map $ g
bar f = (filter f .) . map
所以最后一个f 是唯一棘手的部分,而且这很棘手,因为f 不在表达式的“末尾”。但仔细看,我们看到这是一个函数部分 (. map) 应用于表达式的其余部分:
bar f = (.) (filter f) . map
bar f = (. map) $ (.) $ filter $ f
bar = (. map) . (.) . filter
这就是当你没有像f x x 之类的复杂事物出现在表达式中时减少表达式的方法。通常有一个函数flip f x y = f y x 可以“翻转参数”;您始终可以使用它将f 移动到另一侧。如果您包含显式翻转调用,我们这里有flip (.) map . (.) . filter。
【讨论】:
我要求 lambdabot,一个在各种 Haskell IRC 频道上闲逛的机器人,自动计算出无积分等价物。命令是@pl(毫无意义)。
10:41 <frase> @pl bar f g xs = filter f (map g xs )
10:41 <lambdabot> bar = (. map) . (.) . filter
bar的免积分版是:
bar = (. map) . (.) . filter
这可以说比原始(非无点)代码更难理解。根据具体情况决定是否使用无点样式时,请运用您的良好判断力。
最后,如果你不关心 IRC,还有基于 web 的免点
转换器,例如pointfree.io、pointfree 命令行程序和其他工具。
【讨论】:
cabal install pointfree安装命令行版本。如果你给它自己的 ghci 定义,你可以从 ghci 中使用它:) hackage.haskell.org/package/pointfree