Functional Programming Basic Notes
Lambda Calculus
Lambda Expression (Lambda-Term)
- Variable: x
- Abstraction: λx.M
- Application: M N
e.g. λx.y λx.(λy.xy)
- 变量 x 本身就是一个有效的 lambda 项
- 如果 t 是一个 lambda 项,而 x 是一个变量,则 λx.t 是一个 lambda 项(称为 lambda 抽象)
- 如果 t 和 s 是 lambda 项,那么 (ts) 是一个 lambda 项(称为应用)
Lambda Reduction
α 转换
α: λx.x ≡ λy.y
等价变量替换
β 归约
β: ((λV.E) E′) ≡ E[V := E′]
函数抽象应用(apply)于参数的过程
η 归约
λx.M x ≡ M
用于清除 lambda 表达式中存在的冗余函数抽象
Church Numerals
按照皮亚诺公理可得自然数集合表示为 {0, S(0), S(S(0)), ...}
, 于是得到如下定义:
S ≡ λn.λf.λx.f (n f x)
0 ≡ λf.λx.x
1 ≡ λf.λx.f x
2 ≡ λf.λx.f (f x)
3 ≡ λf.λx.f (f (f x))
...
对后继函数 S 和丘奇数的简单验证如下:
S 0
≡ (λn.λf.λx.f (n f x)) λf.λx.x
= (λn.λg.λy.g (n g y)) λf.λx.x // alpha
= (λf.λx.f (n f x))[n := λf.λx.x] // beta
= λg.λy.g ((λf.λx.x) g y) // substitute
= λg.λy.g (x[f := g, x := y]) // beta
= λg.λy.g y // substitute
= λf.λx.f x // alpha
≡ 1
Definition for Functional Programming
- avoid mutation
- first class functions
- recursive data structures and recursive functions
- laziness
Datatype
Datatype Binding
tagged union, every constructor name as tag, fields for different constructors can't exist at the same time
Built-in Tagged Constructor
- NONE
- SOME i
- []
- x :: xs (infix constructor)
- ()
Type Constructor
type constructor: datatype bindings with variables
datatype 'a myList = EMPTY | CONS of 'a * 'a myList
myList isn't a type, int list is a type
- 'a , 'a equivalent/different
- 'a, 'b different
- ''a, ''a equivalent
Pattern Matching
- null/isSome check tag part(variant)
- hd/tl/valOf check data part (extract data)
case e of
p1 => e1
| pn => en
val p = e (* declare multiple variables once time in p(pattern) *)
(* declare multiple callee arguments(hidden to caller) once time in p(pattern) *)
fun foo p = e
In SML, all functions only take 1 argument, a tuple/record:
fun f (x, y, z) = x + y + z seems that takes 3 arguments, but truly owing to pattern matching only takes 1 tuple argument Likewise, fun f () = 0 takes 1 empty tuple argument.
Further more, tuples is syntactic sugar for records.
As a whole: all functions only take 1 record argument owing to pattern matching.
Tail Position, Tail expression, Tail Call and Tail Recursion
recursive definition for Tail Position:
- if E isn't in tail position, then sub expressions of E aren't in tail position
- if E is in tail position, then some sub expressions of E are in tail position
if eb then e1 else e2
is in tail position, then e1 and e2 are in tail position, not eb
f (x, e)
is in tail position, then f is in tail position(tail call), not x and e
fun factorial n =
let
fun aux(n, acc) =
if
n = 0
then
acc
else
aux (n-1, n*acc)
in
aux (n,1)
end
Rules for expressions
- Syntactic: syntax rules
- Semantic: type checking rules
- Runtime: evaluation rules
samples
syntax: if e1 then e2 else e3
type: e1 = bool, e2 = e3 = any
evaluation: e1 ? e2 : e3
Standard ML
functions
syntax: fun name (arg1: type1, .., argN: typeN) = body
type: name = type1 * ... * typeN -> body_type
lazy evaluation
tuples
(* tuples *)
syntax: e = (e1, ..., en)
type: e1 * ... * en (can become fun's arguments list)
evaluation: #1 e, #2 e, ..., #n e
lists
(* lists *)
syntax: l = [e1, ..., en]
type: [] = elem_type list; hd(head) l = elem_type, tl(tail) x = elem_type list
evaluation: cons = e :: l; null [] = false;
> 6 :: [1, 3, 5]
let expressions
syntax: let
b1 b2 ... bn
in
body
end
type: whole let type = body_type
evaluation: whole let result = body_result
options
- NONE : type = 'a option
- SOME e: type =
e_type
option - isSome: type = 'a option -> bool
- valOf : type = 'a option -> 'a
boolean operations
- e1 andalso e2: keyword
- e1 orelse e2 : keyword
- not e1 : bool -> bool
=
(equal)<>
(not equal)>
<
>=
<=
: require two same type elem
closure
lexical scope vs dynamic scope
- lexical scope: function where defined
- dynamic scope: function where called
compose and pipeline
fun sqrt_of_abs = Math.sqrt o Real.fromInt o abs
infix !>
fun x !> f = f x
fun sqrt_of_abs i = i !> abs !> Real.fromInt !> Math.sqrt
curry and unCurry
fun carry f x y = f (x, y)
fun unCarry f (x, y) = f x y
fun range (i, j) = if i > j then [] else i :: range(i+1, j)
fun countUp = curry range 1
val arr = countUp 7 (* maps to [1, 2, ..., 7] *)