You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell多参数函数中美元符号($)的使用问题解析

Haskell运算符优先级与结合性问题解答

先看你在GHCi里的交互记录:

Prelude> p a b c=1
Prelude> p $ 2 $ 2 $ 5
tons of error......
Prelude> ($$)=($)
Prelude> p $$ 2 $$ 2 $$ 5
1
Prelude> p $$ [2..3] $$ 4 $$ 5
1
Prelude> p $$ [1]++[2..3] $$ 4 $$ 5
tons of error......
Prelude> p $$ [1]++[2..3] $$ [1]++[2..3] $$ 5
of course, tons of error......

接下来逐个解答你的问题:

问题1:为何p $ 2 $ 2 $ 5使用($)会报错?

这完全是$的结合性和优先级导致的。Haskell里内置的$是右结合运算符,优先级是所有运算符里最低的(优先级0)。所以这个表达式会被GHCi解析成:

p $ (2 $ (2 $ 5))

先看最内层的2 $ 5:$的作用是把右边的参数传给左边的函数,但2是个数值,根本不是函数类型,这直接触发类型错误。退一步说,就算这里没问题,最终p也只接收到一个参数,而p需要三个参数,双重错误自然会炸锅。

问题2:为何自定义运算符($$)在p $$ 2 $$ 2 $$ 5中可正常运行?

关键点在于:自定义中缀运算符默认的规则和内置$完全不同。Haskell规定,如果你没有显式声明自定义运算符的优先级和结合性,它默认是左结合,并且优先级是最高的(优先级9)。

所以p $$ 2 $$ 2 $$5会被解析成:

(((p $$ 2) $$ 2) $$ 5)

因为$$等于($)(也就是函数应用),这等价于:

(((p 2) 2) 5)

正好是p接收三个参数的正确方式,自然返回1。

问题3:为何($$)在p $$ [1]++[2..3] $$ 4 $$ 5中会报错?

还是优先级的锅!++运算符的优先级是5,而自定义$$的默认优先级是9(比5高)。优先级高的运算符会先结合,所以这个表达式会被拆成:

((p $$ [1]) ++ [2..3]) $$ 4 $$5

先看p $$ [1],这其实是p [1],返回的是一个需要两个参数的函数(因为p是三元函数)。接下来你试图把这个函数和列表[2..3]用++拼接——函数和列表类型完全不兼容,这就触发了类型错误,自然报错。

问题4:是否存在更优雅的写法来正确实现p $$ [1]++[2..3] $$ [1]++[2..3] $$ 5的功能?

当然有,核心思路是让++的表达式先被计算,再作为参数传给p,这里给你三种常用方案:

方案1:给++表达式加括号

直接用括号把[1]++[2..3]包起来,强制让它先计算:

p $$ ([1]++[2..3]) $$ ([1]++[2..3]) $$ 5

这样因为括号的优先级最高,会先算出两个列表,再通过左结合的$$依次传给p,最终等价于p ([1]++[2..3]) ([1]++[2..3]) 5,完美运行。

方案2:重新声明$$的优先级和结合性

如果你想让$$的行为和内置$类似,但又想用这个名字,可以显式声明它的优先级和结合性,让它比++优先级低:

infixr 0 $$  -- 和内置$一样,右结合,优先级0
($$) = ($)

这样p $$ [1]++[2..3] $$ [1]++[2..3] $$5会被解析成:

p $$ ([1]++[2..3]) $$ ([1]++[2..3]) $$5

因为$$优先级比++低,所以++会先计算,同样能得到正确结果。

方案3:直接用常规函数应用写法

其实最直白的写法反而可能最优雅,完全不用运算符:

p ([1]++[2..3]) ([1]++[2..3]) 5

没有任何运算符优先级的坑,一眼就能看懂。

内容的提问来源于stack exchange,提问作者MaxC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:58:40