在Haskell中没有点

时间:2010-03-17 17:25:48

标签: haskell coding-style pointfree

我有这个代码,我想做无点;

(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))

我该怎么做?

除了“想想这个和某些东西”之外,还有一些点免费风格的一般规则吗?

5 个答案:

答案 0 :(得分:52)

开启功能

func x y z = (some expression in x, y and z)

进入无点形式,我通常会尝试按照对最后一个参数z所做的操作,并将函数写为

func x y z = (some function pipeline built using x and y) z

然后我可以取消z来获取

func x y = (some function pipeline built using x and y)

然后重复y和x的过程应该以无点形式结束func。在这个过程中要认识到的一个重要转变是:

    f z = foo $ bar z    -- or f z = foo (bar z)
<=> f z = foo . bar $ z
<=> f   = foo . bar

同样重要的是要记住,通过部分评估,您可以“中断”函数的最后一个参数:

foo $ bar x y == foo . bar x $ y    -- foo applied to ((bar x) applied to y)

对于您的特定功能,请考虑kt经历的流程:

  1. ord应用于每个人
  2. 添加结果
  3. 减去2 * a
  4. 取结果mod 26
  5. 添加
  6. 申请chr
  7. 因此,作为简化的第一次尝试,我们得到:

    func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ord k + ord t
    

    请注意,您可以通过使用flip上的部分来避免使用mod,而使用-的部分会在Haskell中变得混乱,因此存在subtract函数(它们会与编写负数的语法:(-2)表示否定2,与subtract 2不同。)

    在此功能中,ord k + ord t是使用Data.Function.onlink)的绝佳选择。这个有用的组合器允许我们将ord k + ord t替换为应用于kt的函数:

    func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ((+) `on` ord) k t
    

    我们现在非常接近

    func k t = (function pipeline) k t
    

    因此

    func = (function pipeline)
    

    不幸的是,Haskell在用一系列一元函数编写二进制函数时有点乱,但是有一个技巧(我会看看我是否可以找到一个很好的参考),我们最终得到了:

    import Data.Function (on)
    
    func = ((chr . (+a) . (`mod` 26) . subtract (2*a)) .) . ((+) `on` ord)
    

    这几乎是一个很好的简洁无点的功能管道,除了那个丑陋的构图技巧。通过定义评论on this page中建议的.:运算符,可以稍微整理一下:

    import Data.Function (on)
    
    (.:) = (.).(.)
    
    func = (chr . (+a) . (`mod` 26) . subtract (2*a)) .: ((+) `on` ord)
    

    为了更好地完善这一点,您可以添加一些辅助函数来分隔字母&lt; - &gt;来自Caesar cipher算术的Int转换。例如:letterToInt = subtract a . ord

答案 1 :(得分:10)

  

除了“想想这个和某些东西”之外,还有一些点免费风格的一般规则吗?

你总是可以欺骗并使用lambdabot中的“​​pl”工具(通过转到freenode上的#haskell或使用例如ghci on acid)。对于您的代码pl给出:

((chr . (a +) . flip mod 26) .) . flip flip (2 * a) . ((-) .) . (. ord) . (+) . ord

如果你问我,那真的不是一个改进。

答案 2 :(得分:3)

将表达式转换为无点样式肯定有一系列技巧。我并不认为自己是专家,但这里有一些提示。

首先,您希望在表达式的最右侧术语中隔离函数参数。您的主要工具将是flip$,使用规则:

f a b ==> flip f b a
f (g a) ==> f $ g a

其中fg是函数,ab是表达式。所以开始:

(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
-- replace parens with ($)
(\k t -> chr $ (a +) . flip mod 26 $ ord k + ord t - 2*a)
-- prefix and flip (-)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ord k + ord t)
-- prefix (+)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ (+) (ord k) (ord t))

现在我们需要在右侧获得t。为此,请使用以下规则:

f (g a) ==> (f . g) a

所以:

-- pull the t out on the rhs
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((+) (ord k) . ord) t)
-- flip (.) (using a section)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) $ (+) (ord k)) t)
-- pull the k out
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)

现在,我们需要将kt左侧的所有内容转换为一个大函数术语,以便我们使用(\k t -> f k t)形式的表达式。这是事情变得有点令人费解的地方。首先,请注意直到最后$的所有术语都是具有单个参数的函数,因此我们可以将它们组合起来:

(\k t -> chr . (a +) . flip mod 26 . flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)

现在,我们有一个Char -> Char -> Int类型的函数,我们希望用类型Int -> Char的函数组合,产生类型为Char -> Char -> Char的函数。我们可以使用(非常奇怪的)规则来实现这一目标

f (g a b) ==> ((f .) . g) a b

这给了我们:

(\k t -> (((chr . (a +) . flip mod 26 . flip (-) (2*a)) .) . ((. ord) . ((+) . ord))) k t)

现在我们可以应用beta减少:

((chr . (a +) . flip mod 26) .) . (flip flip (2*a) . ((-) . ) . ((. ord) . (+) .ord))

答案 3 :(得分:3)

我假设你的重点是让代码更简洁,更易读。因此,我认为对简化进行一些其他重构是明智的,这样可以更容易地删除变量。

(\k t -> chr $ a + flip mod 26 (ord k + ord t - 2*a))

首先,flip是不必要的:

(\k t -> chr $ a + (ord k + ord t - 2*a) `mod` 26)

接下来,我将使用 name并征服来分解一个可独立使用的子功能:

encode_characters k t = chr $ encode (ord k) (ord t)
encode x y = (x + y - 2*a) `mod` 26 + a

我还给第一个表达式命名,以使其更清晰和可重用。使用@Nefrubyr:

中的技术,encode_characters现在很容易实现无点
encode_characters = chr . encode `on` ord

至于第二个表达式,我不能生成一个比其他答案中显示的更可读的表单,并且它们的可读性都低于逐点表单。因此,我建议在此时停止重构,并钦佩最终代码的清洁度和可重用性。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~

PS:作为练习,根据问题的上下文,对函数接口进行一些细微的修改(以什么形式传递给函数的数据)可以通过概括问题来产生更多的简化。

一个。实现并简化encode_n_characters :: [Char] -> Char所在的encode_characters k t = encode_n_characters [k, t]函数。结果比专用的双参数函数更简单吗?

B中。实现通过encode'定义的函数encode' (x + y) = encode x y,并使用此函数重新实现encode_characters。两种功能都变得更简单吗?整体实施更简单吗? encode'或多或少可重复使用encode

答案 4 :(得分:0)

连接IRC, #haskellask lambdabot !

<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]