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_typeoption - 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] *)