【问题标题】:Point-free in HaskellHaskell 中的无积分
【发布时间】:2010-03-17 17:25:48
【问题描述】:

我有这段代码,我想让它变得无意义;

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

我该怎么做?

除了“想想这个amd想出什么”之外,还有一些关于无点风格的一般规则吗?

【问题讨论】:

  • 你为什么要让这个点免费?
  • 因为能够编写无点代码看起来像是优秀 Haskell 程序员的属性之一。
  • 有时无点代码比它的非无点代码更清晰,那么使用无点样式是个好主意。这不是那些时代之一。
  • 嗯,您总是可以按照en.wikipedia.org/wiki/… 中的说明进行 SKI 分解,其中 S = Control.Monad.ap、K = const 和 I = id。但这与 Haskellers 使代码点免费的做法相去甚远。我从分析我的代码并试图使其更漂亮的许多经验中学到了很多东西,并学习了新的组合器,例如 Control.Arrow.(***, &&&)、应用符号、Data.Function.on 等。
  • 无点代码通常是一个非常疯狂的天才的标志。无点代码有时是优秀 Haskell 程序员的标志。我不认为这是您可以将简单翻译成无点代码的情况之一。

标签: haskell coding-style pointfree


【解决方案1】:

打开一个函数

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

然后我可以取消zs 来获取

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

因此,作为简化的第一次尝试,我们得到:

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

请注意,您可以通过在 mod 上使用部分来避免 flip,而使用 - 的部分在 Haskell 中会变得混乱,因此有一个 subtract 函数(它们与写入负数的语法冲突:@987654341 @表示负2,与subtract 2不同。

在此函数中,ord k + ord t 是使用 Data.Function.on (link) 的绝佳候选者。这个有用的组合器让我们可以将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)

这几乎是一个很好的整洁的无点函数管道,除了那个丑陋的组合技巧。通过定义 cmets on this page 中建议的 .: 运算符,这可以稍微整理一下:

import Data.Function (on)

(.:) = (.).(.)

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

为了进一步完善这一点,您可以添加一些辅助函数来将字母 Int 转换与 Caesar cipher 算术分开。例如:letterToInt = subtract a . ord

【讨论】:

  • subtract (2*a) == (+(-2*a)).
【解决方案2】:

除了“想想这个amd想出什么”之外,还有一些关于无点风格的一般规则吗?

您始终可以作弊并使用 lambdabot 的“pl”工具(通过转到 freenode 上的#haskell 或使用例如ghci on acid)。对于您的代码,请提供:

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

如果你问我,这并不是真正的改进。

【讨论】:

    【解决方案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 -&gt; f k t) 形式的表达式。这是事情变得有点令人费解的地方。首先,请注意直到最后一个 $ 之前的所有术语都是具有单个参数的函数,因此我们可以组合它们:

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

    现在,我们有一个Char -&gt; Char -&gt; Int 类型的函数,我们想将它与Int -&gt; Char 类型的函数组合,产生一个Char -&gt; Char -&gt; 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))
    

    【讨论】:

    • 使用 Monad、Applicative 或 Arrow 的 -&gt; 实例也是巧妙的技巧。
    • f (g a) ==&gt; f $ g a 并没有真正的帮助。右手边仍然是f $ (g a)。你想要的是功能组合。 f (g a)(f . g) a
    【解决方案4】:

    我假设您的重点是使代码更简洁和更具可读性。因此,我认为明智的做法是对简化进行一些其他重构,这样可能更容易删除变量。

    (\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 and convection 来分解出一个独立可用的子函数:

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

    我还为第一个表达式命名以使其更清晰和可重用。 encode_characters 现在很容易使用来自@Nefrubyr 的技术使无点:

    encode_characters = chr . encode `on` ord
    

    至于第二个表达式,我无法生成比其他答案中显示的任何形式都更具可读性的形式,而且它们的可读性都低于逐点形式。因此,我建议此时停止重构,并欣赏生成的代码的简洁性和可重用性。

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

    PS:作为练习,根据问题的上下文,对函数接口(以何种形式传递给函数的数据)稍作修改,可能会通过概括问题产生更多的简化。

    A.实现并简化函数encode_n_characters :: [Char] -&gt; Char 其中encode_characters k t = encode_n_characters [k, t]。结果是不是比专门的二元函数更简单?

    B.实现通过encode' (x + y) = encode x y 定义的函数encode' 并使用此函数重新实现encode_characters。任何一个功能都变得更简单了吗?总体上实现是否更简单? encode'encode 更可重用还是更少?

    【讨论】:

      【解决方案5】:

      IRC, #haskellask lambdabot ! 上连接:

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

      【讨论】:

      • 尚未发布,因为我正在尝试手动解决,而不是询问 lambdabot...即将进行编辑。
      • 呸,我想出了 let f1 = \a -> (chr .) 。 ((a+).).((翻转 mod 26).).(.ord).(+).((-) (2*a)) 。 ord - 但它给出了错误的结果,我现在不想调试它:)
      猜你喜欢
      • 1970-01-01
      • 2013-12-29
      • 2023-04-10
      • 2013-12-22
      • 1970-01-01
      • 1970-01-01
      • 2018-07-02
      • 2012-04-24
      相关资源
      最近更新 更多