Haskell: 表达式的计算顺序

本文深入探讨Haskell语言的独特计算顺序和求值机制,解析first-class function与curried function的概念,详解Haskell表达式的求值过程及运算符优先级规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Haskell的计算顺序特性

首先需要知道,Haskell使用first class function1,而且使用curried function.

  • first class就是指这种元素可以传给子程序作为参数,可以从子程序里返回,可以赋给变量。Haskell里面的函数什么都能做,与数值无异。我们在下文中不区分“值”的概念,“值”既可以表示一个具体的数,布尔,也可以表示一个具体的函数。
  • curried就是指函数只有一个参数,而返回其余的部分(一个数值或一个低阶函数)作为返回结果。例如,函数类型为(a -> b) -> c -> (d -> d),则其接收一个参数a -> b,而返回一个类型为c -> (d -> d)为结果。

然后需要知道Haskell中表达式的书写格式和求值方式,这也是大部分FP语言中使用的格式。熟悉这种格式才能读懂Haskell表达式,知道式子对应怎样的求值树。

  • Haskell中的默认计算顺序是左结合的。 例如,f g h x的计算顺序是((f g) h) x,函数f接收g为参数。再例如,sin cos 1的计算顺序是(sin cos) 1,其中sin接受cos为参数。我们发现,这个式子在语法上是错误的,正确的计算方式是sin (cos 1)

  • Haskell中定义函数的方式是定义模式匹配的规则。 在求值时,对于第一个token对应的函数,Haskell按照这个函数定义的若干种产生规则,将token序列的头部和这些产生规则依次(从前到后)进行匹配。一旦序列的左侧头部成功匹配了某个产生式的左部,Haskell会把对应的头部序列换为产生式右部的模式,然后继续执行上述的步骤。

  • Haskell中的优先级

    • 优先应用函数,然后应用运算符。运算符级别高的先执行。
    • 在以中缀的方式使用函数时,其优先级为运算符的最高优先级,且默认左结合。也可以使用形如infixl 7 `op`的方式定义中缀的优先级和结合性。

Haskell的求值过程

在不考虑运算符的情况下,我们把Haskell的求值过程用一个输入队列和一个状态栈描述:

  • 系统从输入队列读到一个值,若栈为空,则留在已解析的栈内。比如这个值的类型为(a -> b) -> c -> (d -> d),则期待下一个输入的词法记号对应一个(a->b)的值,从而把栈中的状态apply到这个值,留下函数应用结果c -> (d -> d)类型的值在栈中。
  • 若栈中已有一个值,则将从输入队列读到的值作为参数,传入栈内已有的值(函数)中,进行apply,得到函数值(数或低阶函数)。
  • 如果读到一个中缀函数,则期待栈内已经有一个满足其类型限定的值;中缀函数以栈中值作为参数传入,完成apply,结果保存在栈中。

这也就是为什么sin cos 1是错的。在读入sin时,系统期待下一个读入的值是一个Num a => a类型的数值,但读入的cosNum a => a -> a类型的,无法匹配,从而报错。

在考虑运算符的情况下,我们认为运算符把式子分割成多个不相关的部分,如括号一样。我们先对各个部分进行求值,然后按照运算符的优先级和结合律依次进行处理。type inference,如果有,也是相同的道理。

参考


  1. first class https://www.jianshu.com/p/c054c59c5fda ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值