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:R→R:x∈R↦x2+2x+1
ML语言中的函数正是这种表示方式,它表明 f f f是 R → R R \to R R→R(定义域为 R R R,陪域为 R R R)上的函数
第二种表示方式的优势:
名称
,类型
和映射
分离开“值”
(例如数学中的关系,特定的有序数对)格式:typ -> typ'
说明:typ 指参数的类型(domain type),typ’ 指结果的类型(range type)
fn var : typ => exp
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) ∀x∈Z(x,k)生成长度为n的序列,共n个序列;(4) flatten函数将这些序列合并为一个序列。
(*伪代码*)
pionts2D n =
flatten (tabulate (λx.tabulate (λy.(x, y+1)) n) n)
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 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);
单独列出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通过上述两个技巧直接减少了递归过程中的中间值。