Haskell自定义运算符¤:左/右结合的实现变更及解析差异
嘿,这个问题问到点子上了!在Haskell里切换运算符的左/右结合性,核心其实是运算符的声明方式,而不是运算逻辑本身(除非你的运算不满足结合律,结果会跟着变,但实现上的变更主要在声明部分)。我给你一步步拆解清楚:
自定义运算符时,你需要用infixl(左结合)或infixr(右结合)来明确它的结合性,后面跟一个0-9的优先级数字(数字越大,优先级越高)。这是Haskell解析器决定表达式分组方式的关键依据:
- 左结合版本的
¤:infixl 5 ¤ - 右结合版本的
¤:infixr 5 ¤
只要改这个声明,解析器就会自动调整表达式的分组逻辑——比如你提到的[1,2] ¤ [3,4] ¤ [5,6],左结合时会解析为(([1,2] ¤ [3,4]) ¤ [5,6]),右结合时则是([1,2] ¤ ([3,4] ¤ [5,6]))。
如果你的¤运算本身是可结合的(比如列表拼接++,左/右结合结果完全一致),那你写的运算函数体完全不用改,只换infixl和infixr就行。
但如果运算不满足结合律,那结合性的改变会直接影响结果——不过这不是因为你改了函数实现,而是解析顺序变了。举个直观的例子:
-- 定义一个减法风格的运算符 (¤) :: Int -> Int -> Int x ¤ y = x - y
- 左结合声明
infixl 6 ¤时,1 ¤ 2 ¤ 3会被解析为(1-2)-3 = -4 - 改成右结合
infixr 6 ¤后,表达式会变成1-(2-3) = 2
看到没?函数体完全没变,只是结合性声明改变了解析顺序,结果就不一样了。
假设你自定义的¤是一个列表操作,比如:
-- 左结合声明 infixl 5 ¤ (¤) :: [Int] -> [Int] -> [Int] xs ¤ ys = sum xs : ys -- 把前一个列表的和放到后一个列表开头
左结合时,([1,2] ¤ [3,4]) ¤ [5,6]的计算过程是:(3 : [3,4]) ¤ [5,6] → (3+3+4) : [5,6] → [10,5,6]
如果把声明改成infixr 5 ¤,计算过程就变成:[1,2] ¤ ([3,4] ¤ [5,6]) → [1,2] ¤ (7 : [5,6]) → (1+2) : [7,5,6] → [3,7,5,6]
这里同样只改了结合性声明,函数体一点没动,结果就完全不同了。
- 核心变更:把运算符声明从
infixl换成infixr(或者反过来),优先级数字可以保持不变,除非你需要同时调整运算符的优先级。 - 可选检查:如果你的运算不满足结合律,确认这种结合性的改变是你想要的结果——但实现上不需要修改函数逻辑。
内容的提问来源于stack exchange,提问作者Patrik Iselind

