无点风格的简单Haskell函数

无点风格的简单Haskell函数,haskell,pointfree,Haskell,Pointfree,我试图理解如何在Haskell中将函数转换为无点表示法。我看到了,但它比我要找的更复杂。我觉得我理解它背后的逻辑,但当我试图在代码中执行一些简单的示例时,我会遇到编译错误。我想尝试以无点样式编写此函数: fx=5+8/x我将其重新排列为fx=(+)5$(/)8x 所以,我想可能是这样的: f = (+) 5 $ (/) 8 但当我在ghci中运行此命令时,我得到以下消息: 没有(Num(a0->a0))的实例 源自测试时的文字“5”。hs:3:9 可能的修复方法:为(Num(a0->a0))添

我试图理解如何在Haskell中将函数转换为无点表示法。我看到了,但它比我要找的更复杂。我觉得我理解它背后的逻辑,但当我试图在代码中执行一些简单的示例时,我会遇到编译错误。我想尝试以无点样式编写此函数:

fx=5+8/x
我将其重新排列为
fx=(+)5$(/)8x

所以,我想可能是这样的:

f = (+) 5 $ (/) 8
但当我在ghci中运行此命令时,我得到以下消息:

没有(Num(a0->a0))的实例
源自测试时的文字“5”。hs:3:9
可能的修复方法:为(Num(a0->a0))添加实例声明
在“(+)”的第一个参数中,即“5”
在“($)”的第一个参数中,即“(+)5”
在表达式中:(+)5$(/)8
失败,已加载模块:无。

我不明白“没有…的实例”的信息。我需要做什么才能以无点样式编写此函数?

$
的优先级非常低。所以,
fx=(+)5$(/)8x
实际上意味着
fx=(+)5$(/)8x
。相反,将其改写为

f x = (+) 5 ( (/) 8 x)
f x = ((+) 5) ( ((/) 8) x)
f x = ((+) 5) .  ( ((/) 8) ) x
f = ((+) 5) . ( (/) 8 )
f = (5+) . (8/)
最后一个表达式是有意义的:f是两个运算的组合,首先将8除以1,然后将5加到结果中。请记住,
g.h
的意思是“应用h,然后应用g以获得结果”。

可以使用
cabal install pointfree
安装“无点”程序,并向您展示如何以无点样式编写表达式。例如:

$ pointfree "f x = 5 + 8/x"
f = (5 +) . (8 /)
对这种转换的解释:

  • 您可以使用中缀/运算符函数的“节”<代码>(a+)==\b->a+b和
    (+a)=\b->b+a
  • 函数获取第二个参数的结果,这是一个单参数函数,并将其应用于第一个参数

  • 你真的很接近。请允许我再添加一个
    $
    来说明:

    f x = (+) 5 $ (/) 8 $ x
    
    应该清楚的是,表达式
    (+)5
    是一个接受一个数字输入并产生一个数字输出的函数。表达式
    (/)8
    也是如此。因此,无论输入什么数字,
    x
    ,首先应用
    (/)8
    “函数”,然后应用
    (+)5
    “函数”

    只要有一个由
    $
    分隔的函数链,就可以用
    替换除最右边以外的所有函数。这意味着,如果有
    a$b$c$d
    ,这相当于
    a。B加元d

    f x = (+) 5 . (/) 8 $ x
    
    现在,让我们实际删除
    $
    并用括号代替

    f x = ((+) 5 . (/) 8) x
    
    现在应该很清楚,您可以从两侧移除尾部的
    x

    f = (+) 5 . (/) 8
    
    这是主要的想法。如果您有
    f x=expr x
    ,您可以将其“eta减少”为
    f=expr
    。为了生成无点代码,您只需识别较大的函数是如何由较小的函数组成的。无点代码有时需要部分应用(在这种情况下,部分应用了
    (+)5
    (/)8
    )。当你不想去想它的时候,“无点”程序是非常有帮助的;haskell irc频道上的Lambdabot将此程序用作插件,因此您甚至不必自己安装;只要问问:

    <DanBurton> @pl let f x = 5 + 8 / x in f
    <lambdabot> (5 +) . (8 /)
    
    @pl让f x=5+8/x在f中
    (5 +) . (8 /)
    
    从lambda演算(Haskell是其变体)项到SKI项(完全无点函数,仅使用
    const
    K)、
    id
    I)和
    S)的转换可以通过以下简单规则完成:

  • \x->x
    转换为
    id
  • \x->y
    中不出现
    x
    y
    转换为
    const y
  • \x->f g
    转换为
    f'g'
    其中
    • f'
      \x->f
    • g'
      \x->g
      的翻译
  • 现在您可能想知道
    从何而来。最后一次翻译有一个特例:如果
    f
    没有任何自由出现的
    x
    ,那么
    \x->fg
    将转换为
    常量f(\x->g)
    ,它等于
    f。(\x->g)

    使用这些规则,我们可以转换您的函数:

    f = \x -> ((+) 5) (((/) 8) x) = -- by the special-case (.) rule
    ((+) 5) . (\x -> (((/) 8) x)) = -- by eta-reduction ((\x -> f x) = f)
    ((+) 5) . ((/) 8)
    

    Eta降低不是完成翻译的必要条件,但如果没有它,我们会变得更混乱。例如,最后一步将产生
    (++)5)。((/) 8) . id
    取而代之。

    我想你可能会对这个问题感到困惑。我个人强迫自己使用无点风格,因为:1。我被迫使我的函数更简单,从而使它们更可重用2。实际上,我很可能会费心重用已经编写好的函数,从而减少代码重复。请注意,(.)是关联的,而($)不是,因此(.)在重构方面提供了更大的灵活性。要使用(.)你需要掌握无点风格。此外,一元组合符,如liftM2、fmap、>>=和>=>几乎迫使您学习无点风格。@JFritsch-point-free并不总是编写函数的最清晰方式。然而,在适当的时候,它可以帮助你对函数进行推理,因为无点函数只是其他函数的组合。这实际上很好:所有三个投票结果都显示了问题的不同角度。