应用程序($)操作员意外地执行操作

时间:2014-02-17 21:58:51

标签: haskell operators parameter-passing

我正在编写一个根据起始编号生成Collat​​z链的函数,但是我遇到了一个意想不到的问题

这是代码:

-- original, works
collatzA :: Integer -> [Integer]
collatzA 1 = [1]
collatzA n
      | even n = n:collatzA (n `div` 2)
      | odd  n = n:collatzA (n * 3 + 1)

-- what I'm trying to do, won't compile, gives nasty errors
collatzB :: Integer -> [Integer]
collatzB 1 = [1]
collatzB n
      | even n = n:collatzB $  n `div` 2
      | odd  n = n:collatzB $  n * 3 + 1

-- attempted solution, works but re-adds the parentheses I tried to get rid of
collatzC :: Integer -> [Integer]
collatzC 1 = [1]
collatzC n
      | even n = n: (collatzC $  n `div` 2)
      | odd  n = n: (collatzC $  n * 3 + 1)

那么为什么collatzAcollatzC有效,但collatzB没有?

2 个答案:

答案 0 :(得分:4)

此问题是由运营商优先或修复引起的。

例如(摘自RWH,我强烈推荐)(+)被声明为左关联,固定为6,(*)被声明为左关联,固定为7。表示

8 + 7 + 6 * 5 * 4

被解析为

(8 + 7) + ((6 * 5) * 4)

同样在您的示例中,cons运算符(:)是右关联的并且具有固定性5,而应用程序运算符($)是右关联的并且具有固定性0。 由于($)的固定性低于(:),因此对collatzB的递归调用被(:)“抓取”

n = (n:collatzB) $ (n `div` 2)

This link包含Prelude功能的固定性信息,您还可以查看this post以获取更多信息。

答案 1 :(得分:1)

问题是编译器将f $ g视为(f) $ (g)。如果您有f $ g $ h,编译器会将其视为(f) $ ((g) $ (h)),您可以扩展此模式。所以,当你有

n : collatzB $ n `div` 2`

编译器将其视为

(n : collatzB) $ (n `div` 2)

并且(n : collatzB)没有输入检查。

这是由于$的固定性及其右关联(infixr)。


如果parens打扰你那么多(他们不应该这样做),你可以定义一个新的运算符

infixr 1 $:
($:) :: a -> (b -> [a]) -> b -> [a]
a $: f = \x -> a : f x

collatzB :: Integer -> [Integer]
collatzB 1 = [1]
collatzB n
    | even n = n $: collatzB $ n `div` 2
    | odd  n = n $: collatzB $ n * 3 + 1

但诚实地说这会造成更多的混乱而不是它的价值。我个人会坚持使用parens。