• programming-languages学习笔记--第3部分


    programming-languages学习笔记–第3部分

    programming-languages学习笔记–第3部分

    1 术语

    函数式编程有几种不同的概念,最重要的两点:

    • 在大部分/所有情况下避免可变性(mutation)
    • 像使用值一样使用函数

    还有其它相关概念:

    • 递归风格和递归数据结构
    • 编程风格和语法接近传统数学定义的函数
    • 使用惰性的编程模型

    first-class function: 函数作为一等公民,像其它值一样可以在任何地方使用。

    fun double x = 2 * x
    fun incr x = x + 1
    val a_tuple = (double, incr, double(incr 7))
    val eighteen = (#1 a_tuple) 9
    
    val double = fn : int -> int
    val incr = fn : int -> int
    val a_tuple = (fn,fn,16) : (int -> int) * (int -> int) * int
    val eighteen = 18 : int
    
    

    function Closures: 函数闭包是指函数使用在它外面定义的变量。 higher-order function:高阶函数是指接受函数作为参数或返回其它函数的函数

    2 函数作为参数

    把函数作为参数传递给另外一个函数,组织常用代码的优雅策略。

    fun increment_n_times_lame (n, x) = (* n + x *)
        if n = 0 
        then x
        else 1 + increment_n_times_lame(n-1, x)
    
    fun double_n_times_lame (n, x) = (* 2^n * x *)
        if n = 0
        then x
        else 2 * double_n_times_lame(n-1, x)
    
    fun nth_tail_lame (n, xs) = (* 3, [4,8,12,16] -> 16 *)
        if n = 0 
        then xs
        else tl (nth_tail_lame(n-1, xs))
    
    (** 上面的三个函数有共同特征,提取共同点,形成新的模型,更好地组织代码 *)
    fun n_times (f, n, x) = 
        if n = 0
        then x
        else f (n_times(f, n-1,x))
    (** 等同于 f(f(f(f ... (x)))) **)
    
    fun increment x = x + 1
    fun double x = x + x
    
    val x1 = n_times(double, 4, 7)
    val x2 = n_times(increment, 4, 7)
    val x3 = n_times(tl, 2, [4,8,12,16])
    
    fun addition(n,x) = n_times(increment,n,x)
    fun double_n_times(n,x) = n_times(double,n,x)
    fun nth_tail(n,x) = n_times(tl,n,x)
    
    (* 新的功能更容易添加                           *)
    fun triple x = 3 * x
    fun triple_n_times(n,x) = n_times(triple,n,x)
    
    val increment_n_times_lame = fn : int * int -> int
    val double_n_times_lame = fn : int * int -> int
    val nth_tail_lame = fn : int * 'a list -> 'a list
    val n_times = fn : ('a -> 'a) * int * 'a -> 'a
    val increment = fn : int -> int
    val double = fn : int -> int
    val x1 = 112 : int
    val x2 = 11 : int
    val x3 = [12,16] : int list
    val addition = fn : int * int -> int
    val double_n_times = fn : int * int -> int
    val nth_tail = fn : int * 'a list -> 'a list
    val triple = fn : int -> int
    val triple_n_times = fn : int * int -> int
    

    3 多态类型和函数作为参数

    Higher-order函数更通用和可重用是因为它们有多态类型。

    参数化多态(parametric polymorphism),也叫泛型(generic types)。函数可以接受任何类型的参数。

    4 匿名函数

    辅助函数最好不要暴露在外面,使用匿名函数解决这个问题。

    fun triple_n_times2 (n,x) = 
        let fun trip y = 3 * y
        in
            n_times(trip,n,x)
        end
    
    fun triple_n_times3 (n,x) = 
        n_times(let fun triple x = 3*x in triple end,n,x)
    
    (* 使用匿名函数更好的表达 *)
    fun triple_n_times4 (n,x) = 
        n_times(fn x => 3*x,n,x)
    
    val triple_n_times2 = fn : int * int -> int
    val triple_n_times3 = fn : int * int -> int
    val triple_n_times4 = fn : int * int -> int
    
    

    匿名函数常用来作为高阶函数(higher-order function)的参数。但不能用于递归函数,因为没有函数名。fun绑定是val绑定加上匿名函数的语法糖:

    fun triple x = 3 * x
    val triple = fn y => 3*y
    

    5 不必要的函数包装

    多余的包装(poor style):

    if x then true else false

    (fn x => tl x)

    6 Map 和 Filter

    最常用的两个高阶函数。

    fun map(f,xs) = 
        case xs of 
            [] => []
         | x::xs' => (f x)::map(f,xs')
    
    val x1 = map((fn x => x + 1), [4,5,6,7])
    val x2 = map(hd, [[1,2],[3,4],[5,6,7]])
    
    fun filter(f,xs) = 
        case xs of 
            [] => [] 
         |  x::xs' =>
            if f x
            then x::(filter(f, xs'))
            else filter(f, xs')
    
    fun is_even v = (v mod 2 = 0)
    fun all_even xs = filter(is_even, xs)
    
    fun all_even_snd xs = filter((fn (_,v) => is_even v), xs)
    val x = all_even [3,4,6,0,13]
    val y = all_even_snd [(3,4),(5,6),(8,7),(0,9)]
    
    
    val map = fn : ('a -> 'b) * 'a list -> 'b list
    val x1 = [5,6,7,8] : int list
    val x2 = [1,3,5] : int list
    val filter = fn : ('a -> bool) * 'a list -> 'a list
    val is_even = fn : int -> bool
    val all_even = fn : int list -> int list
    val all_even_snd = fn : ('a * int) list -> ('a * int) list
    val x = [4,6,0] : int list
    val y = [(3,4),(5,6)] : (int * int) list
    
    

    7 返回函数

    返回一个函数:

    fun double_or_triple f = 
        if f 7 
        then fn x => 2 * x
        else fn x => 3 * x 
    
    val double_or_triple = fn : (int -> bool) -> int -> int
    
    

    注意函数的类型,等价于(int -> bool) -> (int -> int), 因为->是右结合的。

    8 词法作用域

    Lexical Scope,在函数体内可以使用任何作用域内的绑定,但是函数可以传递(作为参数或返回值):它的作用域在哪里?答案是函数定义的地方(大部分编程语言),不是调用的地方。这样的语义叫做词法作用域。

    val a = 3 
    fun test b = 
        "test " ^ Int.toString a ^ " " ^ b
    val a = 5
    val r = test "woh"
    
    val a = <hidden-value> : int
    val test = fn : string -> string
    val a = 5 : int
    val r = "test 3 woh" : string
    
    

    另外一种是动态作用域,环境在函数调用的地方。

    (setq a 3)
    (defun test (b)
      (message "test %s %s" a b))
    (setq a 5)
    ;可以看到emacs lisp是动态作用域,c中的#define宏展开也是动态作用域,在展开的地方获
    ;得当前环境
    (test "woh")
    
    test 5 woh
    
    

    9 环境和闭包

    编程语言的实现保存了函数定义时需要的环境,用来实现词法作用域。

    一个函数值有两个部分

    • 要执行的代码
    • 函数定义(创建)时的环境

    一个函数调用在环境部分(加上函数参数)中求值代码部分。

    这两个部分就叫做函数闭包(function closure),这样做的原因是函数的代码可以拥有自由变量(不是在函数的代码中绑定的变量,所以它们需要绑定到一些外部环境中),闭包附带一个提供所有这些绑定的环境。因此闭包是"封闭"的——它拥有给定一个函数参数,然后产生函数结果所需要的一切东西。

    10 词法作用域和高阶函数

    规则相同,函数体在函数定义(创建)的环境中求值(加上函数参数)。

    val x = 1
    
    fun f y = 
        let 
            val x = y + 1
        in
            fn z => x + y + z (* 2y + 1 + z*)
        end
    
    val x = 3 (*不相关 *)
    val g = f 4
    val y = 5 (*不相关 *)
    val z = g 6
    
    val x = <hidden-value> : int
    val f = fn : int -> int -> int
    val x = 3 : int
    val g = fn : int -> int
    val y = 5 : int
    val z = 15 : int
    
    

    11 为什么用词法作用域

    • 词法作用域:使用函数定义时的环境
    • 动态作用域:使用函数调用时的环境

    使用词法作用域的优点:

    • 函数的语义不依赖于使用的变量名
    • 函数在定义的地方可以进行类型检查和推断
    • closure可以很容易地保存需要的数据

    异常处理更像是动态作用域: raise e查找要求值的处理表达式。这个查找过程使用动态的调用栈。

    12 闭包和重复计算

    已知:

    • 函数体直到函数调用时才会求值
    • 在函数调用时,函数体每次都会求值
    • 变量绑定在绑定时求值它的表达式,不是变量使用时

    对于闭包来说,这就意味着我们可以避免不依赖函数参数的重复计算。

    13 Fold和更多闭包

    fold,reduce,inject表示的是同一个意思,是另外一个常用的用于递归数据结构的高阶函数。

    fun fold (f,acc,xs) = 
        case xs of 
            [] => acc
         |  x::xs' => fold(f, f(acc,x), xs')
    
    (* sum list *)
    fun f1 xs = fold ((fn (x,y) => x+y), 0, xs) 
    
    (* 所有元素都大于零? *)
    fun f2 xs = fold ((fn (x,y) => x andalso y >= 0), true ,xs)
    
    fun f4 (xs,s) = 
        let 
            val i = String.size s
        in
            fold((fn (x,y) => x andalso String.size y < i), true, xs)
        end
    
    fun f5 (g,xs) = fold((fn(x,y) => x andalso g y), true, xs)
    
    fun f4_2 (xs,s) = 
        let 
            val i = String.size s
        in
            f5(fn y => String.size y < i, xs)
        end
    
    val fold = fn : ('a * 'b -> 'a) * 'a * 'b list -> 'a
    val f1 = fn : int list -> int
    val f2 = fn : int list -> bool
    val f4 = fn : string list * string -> bool
    val f5 = fn : ('a -> bool) * 'a list -> bool
    val f4_2 = fn : string list * string -> bool
    
    
    • map,filterfold 这样的函数借助于闭包和词法作用域变得很强大。
    • 函数传递的参数可以使用它自己环境中的任意私有数据
    • 迭代器不需要知道它的数据和类型

    14 closure用法:组合函数

    fun compose(f,g) = fn x => f(g x)
    
    fun sqrt_of_abs i = Math.sqrt (Real.fromInt (abs i))
    fun sqrt_of_abs2 i = (Math.sqrt o Real.fromInt o abs) i
    val sqrt_of_abs3 = Math.sqrt o Real.fromInt o abs
    
    val compose = fn : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b
    val sqrt_of_abs = fn : int -> real
    val sqrt_of_abs2 = fn : int -> real
    val sqrt_of_abs3 = fn : int -> real
    
    

    上面使用了sml内置的中缀操作符o,创建自己的中缀操作符:

    infix !>
    fun x !> f = f x
    fun sqrt_of_abs i = i !> abs !> Real.fromInt !> Math.sqrt
    

    15 闭包用法:Currying

    fun sorted3_tupled (x,y,z) = z >= y andalso y >= x
    
    val t1 = sorted3_tupled (7,9,11)
    
    (* 使用currying *)
    val sorted3 = fn x => fn y => fn z => z >= y andalso y >= x
    (* soretd3 x = fn y => fn z => ... *)
    
    (* 使用currying才能这样调用  *)
    val t2 = (sorted3 7) 9 11
    val t3 = sorted3 7 9 11 
    
    (* 语法糖 *)
    fun sorted3_nicer x y z = z >= y andalso y >= x 
    val t4 = sorted3_nicer 7 9 11
    
    val sorted3_tupled = fn : int * int * int -> bool
    val t1 = true : bool
    val sorted3 = fn : int -> int -> int -> bool
    val t2 = true : bool
    val t3 = true : bool
    val sorted3_nicer = fn : int -> int -> int -> bool
    val t4 = true : bool
    
    

    柯里化(currying)相当于定义了fn列表,fn->fn…->fn,函数只能接受一个参数的原因前面已经说过(核心语言简单,方便作为参数和返回值)。

    16 Partial Application

    前面使用柯里化模拟多个参数,但是如果提供的参数太少,就会得到一个等待剩余参数的闭包:

    • 叫做部分应用
    • 便利并且有用
    • 可以用于任意柯里化函数

    避免不必要的函数包装

    fun range i j = if i > j then [] else i :: range (i+1) j
    val r1 = range 3 6
    val countup = range 1
    val r2 = countup 6
    
    val range = fn : int -> int -> int list
    val r1 = [3,4,5,6] : int list
    val countup = fn : int -> int list
    val r2 = [1,2,3,4,5,6] : int list
    
    

    curry and uncurry:

    fun other_curry f x y = f y x
    fun curry f x y = f (x,y)
    fun uncurry f (x,y) = f x y
    
    val other_curry = fn : ('a -> 'b -> 'c) -> 'b -> 'a -> 'c
    val curry = fn : ('a * 'b -> 'c) -> 'a -> 'b -> 'c
    val uncurry = fn : ('a -> 'b -> 'c) -> 'a * 'b -> 'c
    
    

    17 Mutable References

    当你的模型需要更新状态的情况下,要用到mutation。

    新类型: t ref 是引用类型,t是一个类型。

    新表达式:

    • ref e 创建一个引用,初始内容为e
    • e1 := e2 更新内容
    • !e 获取内容
    val x = ref 42
    val y = ref 42
    val z = x
    val _ = x := 43
    val w = (!y) + (!z)
    
    val x = ref 43 : int ref
    val y = ref 42 : int ref
    val z = ref 43 : int ref
    val w = 85 : int
    
    

    x,z指向同样的内容。

    18 Closure用法:回调

    回调是常用的:当一个库接受一个函数用于当一个事件发生时侯调用,例如:

    • 当一个按键按下,鼠标移动,数据到达时。
    • 当程序进入某种状态时
    (* server  *)
    val cbs : (int -> unit) list ref = ref []
    
    fun onKeyEvent f = cbs := f :: (!cbs)
    
    fun onEvent i = 
        let fun loop fs = 
                case fs of 
                    [] => ()
                  | f::fs' => (f i; loop fs')
        in
            loop(!cbs)
        end
    
    (* client *)
    val timesPressed = ref 0 
    val _ = onKeyEvent (fn _ => 
                           timesPressed := (!timesPressed) + 1)
    
    fun printIfPressed i = 
        onKeyEvent (fn j => 
                   if i = j
                   then print ("you pressed " ^ Int.toString i)
                   else ())
    
    val _ = printIfPressed 4
    val _ = printIfPressed 12
    val _ = printIfPressed 5;
    onEvent 3;
    onEvent 22;
    onEvent 5;
    onEvent 4;
    !timesPressed;
    
    val cbs = ref [fn,fn,fn,fn] : (int -> unit) list ref
    val onKeyEvent = fn : (int -> unit) -> unit
    val onEvent = fn : int -> unit
    val timesPressed = ref 0 : int ref
    val printIfPressed = fn : int -> unit
    val it = () : unit
    val it = () : unit
    you pressed 5val it = () : unit
    you pressed 4val it = () : unit
    val it = 4 : int
    

    19 ADT with Closures

    closures可以实现Abstract Data Types:

    • 在一个record中存放多个函数
    • 这些函数共享同样的私有数据
    • 私有数据可变或不可变
    • 感觉像对象,表示OOP和函数式编程有很多相似的地方
    datatype set = S of { insert : int -> set,
                          member : int -> bool,
                          size : unit -> int }
    
    val empty_set = 
        let 
            fun make_set xs = 
                let 
                    fun contains i = List.exists (fn j => i=j) xs 
                in
                    S { insert = fn i => if contains i 
                                         then make_set xs 
                                         else make_set (i::xs),
                      member = contains,
                      size = fn () => length xs}
                end 
        in
            make_set [] 
        end
    
    (* example client *)
    fun use_sets () = 
        let val S s1 = empty_set
            val S s2 = (#insert s1) 34
            val S s3 = (#insert s2) 34
            val S s4 = #insert s3 19
        in
            if (#member s4) 42 
            then 99 
            else if (#member s4) 19 
            then 17 + (#size s3) ()
            else 0
        end
     val v = use_sets()
    
    datatype set = S of {insert:int -> set, member:int -> bool, size:unit -> int}
    val empty_set = S {insert=fn,member=fn,size=fn} : set
    val use_sets = fn : unit -> int
    val v = 18 : int
    
    

    20 没有closure的情况

    Higher-order programming,在没有closure的编程语言中也可以实现:

    • OOP中(如java)使用单方法接口
    • 过程式语言中(如c)使用显式传递环境变量

    21 为什么学习通用PL概念?

    什么是最好的编程语言? 就像什么是最好的车? 什么是最好的鞋子?一样,没有最好的某一个,针对不同目的有不同的设计。比如小型车、SUV、MPV,虽然都能完成车的功能,但针对不同的用途有不同的设计。

    • 一个好的机械工程师有一些特长,但也要理解汽车怎么工作
      • 内饰是不重要的(语法)
    • 一个好的机械工程师知道汽车怎么工作,怎么让它们充分发挥作用,并知道怎么设计更好的
      • 没有最爱的汽车或最爱的编程语言
    • 学习汽车的组成,最好找一个老款的,模型比较简单,最新的会添加很多功能,比较复杂。

    作者: ntestoc

    Created: 2018-12-15 Sat 22:38

  • 相关阅读:
    介绍 Jersey 依赖
    Jersey 2.x 分支 Java SE 兼容性
    Confluence 6 管理 Atlassian 提供的 App
    Confluence 6 针对站点维护使用只读模式
    Confluence 6 协同编辑问题解决
    Confluence 6 管理协同编辑
    Confluence 6 管理协同编辑
    Confluence 6 管理协同编辑
    Confluence 6 管理协同编辑
    Confluence 6 管理协同编辑
  • 原文地址:https://www.cnblogs.com/ntestoc/p/10118031.html
Copyright © 2020-2023  润新知