我正在编写一个根据起始编号生成Collatz链的函数,但是我遇到了一个意想不到的问题
这是代码:
-- 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)
那么为什么collatzA
和collatzC
有效,但collatzB
没有?
答案 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)
答案 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。