sml基本语法(三)——函数

写在前面

  • 本博客更类似博主本人的学习笔记,请各位谨慎参考,以防被误导
  • 由于写本篇时对函数式编程仍然理解不够深入,现在看来命令式思维很重,但是作为sml入门来说应该有些心得的确能帮助到大家

语法原型(数学中)

通常表示函数,是
f ( x ) = x 2 + 2 x + 1 f(x) = x^2+2x+1 f(x)=x2+2x+1

数学中,函数还有其他表示方法
f : R → R : x ∈ R ↦ x 2 + 2 x + 1 f: R\to R:x \in R \mapsto x^2+2x+1 f:RR:xRx2+2x+1

ML语言中的函数正是这种表示方式,它表明 f f f R → R R \to R RR(定义域为 R R R,陪域为 R R R)上的函数

第二种表示方式的优势

  • 将函数的名称类型映射分离开
  • 函数更像是“值”(例如数学中的关系,特定的有序数对)

函数的类型( f u n c t i o n function function t y p e type type

格式:typ -> typ'
说明:typ 指参数的类型(domain type),typ’ 指结果的类型(range type

函数的“值”(两种类型)

  1. 基本函数(primitive functions),例如 加函数、开方函数
  2. λ \lambda λ表达式(function expressions
    格式:fn var : typ => exp
    说明:var被称作形式参数,typ是它的类型;exp被称作函数的body(内容)
    举例:fn x : real => Math.sqrt (Math.sqrt x)
       这是一个开四次方功函数
       (fn x : real => Math.sqrt (Math.sqrt x)) (16.0)
       我们说这个函数是“匿名的”

补充:很多书上会以 λ x . e \lambda x.e λx.e表示函数,这就是 λ \lambda λ函数的另一种写法,这种写法更能体现函数就是一个value,其中x是形参,e是表达式,示例如下:(1) 该函数作用是生成平面上的第一象限整点序列(包括xy轴);(2) 先看最内层括号 λ y . ( x , y + 1 ) \lambda y.(x, y+1) λy.(x,y+1)接收形参y返回元组 ( x , y + 1 ) (x, y+1) (x,y+1),于是在tabulate作用下形成新的关于x的映射序列 < ( x , 1 ) , ( x , 2 ) , . . . , ( x , n ) > <(x,1),(x,2),...,(x,n)> <(x,1),(x,2),...,(x,n)>;(3) 在最外层tabulate作用下,对每个 ∀ x ∈ Z ( x , k ) \forall x\in Z(x,k) xZ(x,k)生成长度为n的序列,共n个序列;(4) flatten函数将这些序列合并为一个序列。

(*伪代码*)
pionts2D n =  
	flatten (tabulate (λx.tabulate (λy.(x, y+1)) n) n)

声明方式(两种)

  • val 声明法,格式为val var1 : typ1 = exp1(详见sml基本语法(二))
val fourthroot : real -> real =
	fn x : real => Math.sqrt (Math.sqrt x)

这相当于将 fn x : real => Math.sqrt (Math.sqrt x) 绑定到 fourthroot

  • fun声明
    格式:fun fname (var:dtyp):rtyp = fexps
    说明:fname 函数名,var参数,dtyp参数类型,rtyp返回值类型,fexps操作表达式其结果为返回值,
    本质val声明的简写版,fn记法就是函数表示成值的一种记法,由此可见函数声明和ML中其它变量声明没有区别
fun fourthroot (x:real):real = Math.sqrt (Math.sqrt x)

执行过程

遵守call-by-value的原则:函数参数的求值要优先于函数的调用

///~eg:
fun fourthroot (x:real):real = Math.sqrt (Math.sqrt x);
fourthroot (2.0+2.0);
  1. 计算fourthroot函数的值为 fn x : real => Math.sqrt (Math.sqrt x);
  2. 计算参数表达式 2.0+2.0的值为 4.0;
  3. 将形参x赋值为 4.0;
  4. 计算 Math.sqrt (Math.sqrt x) 的值为 1.414 (近似):(过程如下)
    a) 计算Math.sqrt的值 (为基本函数中的root function);
    b) 计算参数表达式 Math.sqrt x的值, 约等于2.0;
    i. 计算Math.sqrt的值 (为基本函数中的root function);
    ii.已绑定的x的值为 4.0;
    iii. 计算4.0的正算术平方根结果为 2.0;
    c)计算2.0的正算术平方根结果为 1.414;
  5. 回收 x所绑定值的所占空间。

作用域及句法

单独列出ml语言中函数作用域的特性意义不大,因为它完全遵照static, lexical这两个特性,所以通过举例来加深理解:
1.同名变量

///~eg:
fun h(x:real) = 
	let val x = 2.0 in x+x end * x;

(1) let语句中的 x x x会覆盖 h h h函数中形参 x x x(相当于开辟新栈),自然let语句的值(即let语句body的值,in后的表达式的值)所使用的 x x x 2.0 2.0 2.0
(2) let语句结束(相当于出栈), x x x即为 h h h的形参 x x x

2.无return语句
ML为函数式语言,无return语句,自然将表达式的值直接作为返回的结果。一个函数返回一个值(结果),函数内部不存在多个并列的结果,换句话说函数定义的=后只能接一个表达式
对1中的例子而言=后的表达式就是一个实数乘法表达式(real1*real2),let语句的值只作为中间结果。
但有时并列的写一些无关的、不作为返回结果的表达式可以便于我们书写代码,那么如何实现呢?
实际上使用命令组即可
      ( E 1 ; E 2 ; . . . ; E n ) (E_1;E_2;...;E_n) (E1;E2;...;En)
该表达式的值为 E n E_n En的值,其他表达式的值均会被丢弃。
ps:括号一般不会省去(只有在let语句中的exp中可省去),可参考

3.避免繁杂的if else语句
ML语言中支持称为“模式”的书写方式,这样可以避免多个if-else嵌套写到底,下面以返回表中最大元素为例(部分代码, m a x L I s t maxLIst maxLIst实际返回 p 1 p1 p1(指形参1,下同)的降序表):

///~eg:
fun maxList([], ys) = ys
    | maxList(x::xs, []) = maxList(xs, x::[])
	| maxList(x::xs, ys) = 
	if x > hd ys then maxList(xs, x::ys) else maxList(xs, ys);

使用| m a x L i s t maxList maxList函数分为不同的匹配模式:
(1).p1为空表时,
(2).p2为空表时,
(3).p1, p2均不为空表时。
省去不必要的if-else,只对算法真正需要的地方使用if-else,不仅可以是代码结构清晰,有时可以更好地避免一些边界情况。

4.递归式的程序
虽然ML语言支持使用while命令式循环语句

while E1 do E2
///等价于,(实际上被递归地定义为)
while E1 do E2 = 
    if E1 then (E2; while E1 do E2)
    else ();

但这样显然不是函数式编程的风格。
众所周知,递归式的程序虽然结构清晰,但频繁的出入栈操作对时空资源的开销很大,所以如何在时间和空间上平衡是编写较为高效的递归程序的关键,下面用两个常用技巧作为例子:

  • 纯递归=>迭代式递归
    这种技巧常用于已知确切的递归次数

  • 使用额外的标签
    例如表操作中尽量少用追加@,直接在方法中使用::添加在新的表标签中

///~eg1:
fun fact (n) = 
	if n = 1 then 1 else n*fact(n-1);

///~eg2:
fun fact(n, p) = 
	if n = 0 then p else fact(n-1, n*p);
(*调用时p初值为1*)

与eg1相比,eg2通过上述两个技巧直接减少了递归过程中的中间值。

你可能感兴趣的:(sml)