zoukankan      html  css  js  c++  java
  • programming-languages学习笔记--第2部分

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

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

    1 自定义类型

    • 基本类型 (int bool unit char)
    • 复合类型 (tuples, lists, options)

    构造复合类型:

    • each of: 组合所有 例如:tuples, (int * bool) 包含一个int和一个bool, 与java中带的字段类相似
    • one of: 选择一个 例如:option, int option可以包含一个int或者空,java中可以通过子类化实现。
    • self reference: 自引用类型,用于构造递归数据类型,例如列表和树

    list使用了全部3种构造

    2 Records

    属于"each-of"类型:

    • record的值用字段来保存
    • record的类型也用字段保存
    • 字段的顺序无关紧要

    与tuple相比更容易记忆(有字段名),

    注意代码中-开始的行是repl的输入,下面是输出。

    - val x = {bar = (1+2, true andalso true), foo = 3+4, baz = (false,9)};
    val x = {bar=(3,true),baz=(false,9),foo=7}
            : {bar:int * bool, baz:bool * int, foo:int}
    - #bar x;
    val it = (3,true) : int * bool
    - #foo x;
    val it = 7 : int
    

    ML中不需要定义record类型,直接写record表达式,类型检查器会给出正确的类型。

    3 tuples as syntactic sugar

    records和tuples很相似。都是"each-of"构造。唯一的不同是records通过名称访问,tuples通过位置访问。 java的方法调用组合了这两种方式:方法内使用变量名访问不同的参数,但调用者通过位置传递参数。

    可以通过构造records来定义tuples:

    - - val a_pair = (3+1, 4+2);
    val a_pair = (4,6) : int * int
    
    - - val a_record = {second=4+2, first=3+1};
    val a_record = {first=4,second=6} : {first:int, second:int}
    
    - - val another_pair = {2=5, 1=6};
    val another_pair = (6,5) : int * int
    
    - - #1 a_pair + #2 another_pair;
    val it = 9 : int
    
    - - val x = {3="hi", 1=true};
    val x = {1=true,3="hi"} : {1:bool, 3:string}
    
    - - val y = {3="hi", 1=true, 2=3+2};
    val y = (true,5,"hi") : bool * int * string
    

    实际上,tuple就是record, tuple是record的语法糖

    方便设计实现(语言核心足够简单),方便理解

    4 datatype bindings

    datatypee mytype = TwoInts of int * int
                     | Str of string
                     | Pizza
    

    每个mytype的值都从一个构造器产生, 每个值包括:构造的tag, 对应的数据

    上面的示例创建一个新类型 mytype 和3个构造器 TwoInts, Str, Pizza

    构造器两种表示,它可以是创建新类型值的函数(如果有 of t ),或者是新类型的值。 上面的示例中, TwoInts 是一个函数,类型为 int*int -> mytype, Str 是一个类型为 string->mytype 的函数, Pizza 是类型为 mytype 的值。

    要访问datatype的值,需要:

    1. 检查是哪个构造器构造的
    2. 提取数据

    例如: null 和 isSome检查; ht,tl 和 valOf 提取数据

    - - datatype exp = Constant of int
                     | Negate of exp
                     | Add of exp * exp
                     | Multiply of exp * exp ;
    datatype exp
      = Add of exp * exp | Constant of int | Multiply of exp * exp | Negate of exp
    
    - - val r = Add (Constant (10 + 9), Negate (Constant 4));
    val r = Add (Constant 19,Negate (Constant 4)) : exp
    
    - - fun eval e =
            case e of
                Constant i     => i
             |  Negate e2      =>  ~ (eval e2)
             |  Add(e1, e2)    => (eval e1) + (eval e2)
             |  Multiply(e1,e2) =>  (eval e1) * (eval e2)
            ;
    = val eval = fn : exp -> int
    
    - - eval r;
    val it = 15 : int
    
    

    5 case 表达式

    ML使用case表达式和模式匹配访问"one-of"值

    - - datatype mytype = TwoInts of int * int
                        | Str of string
                        | Pizza
                                   ;
    = datatype mytype = Pizza | Str of string | TwoInts of int * int
    
    - fun f (x : mytype) =
          case x of
              Pizza => 3
           |  Str s  =>  8
           |  TwoInts(i1, i2) => i1 + i2
                                   ;
    = val f = fn : mytype -> int
    
    - - f Pizza;
    val it = 3 : int
    
    - - f (TwoInts (7, 9));
    val it = 16 : int
    

    在某种意义上,case表达式就像一个更强大的if-then-else表达式。它首先求值 caseof 中间的表达式,然后求值第一个匹配的分支的表达式。和条件表达式一样,每个分支表达式必须有相同的类型,作为case表达式的值。

    对于每个分支 p => e p是一个模式,e是一个表达式;使用|分割多个分支。模式看起来像表达式,但不要把它们想做表达式。它们用来匹配case的第一个表达式( case 后面的部分)的值。这就是为什么case表达式叫做模式匹配。

    6 类型同义词

    type aname = t

    两个名字可以互换使用

    - datatype suit = Club | Diamond | Heart | Spade ;
    datatype suit = Club | Diamond | Heart | Spade
    
    - datatype rank = Jack | Queen | King | Ace | Num of int;
    datatype rank = Ace | Jack | King | Num of int | Queen
    
    - type card = suit * rank;
    type card = suit * rank
    
    - type name_record = { student_num : int option,
                           first : string,
                           middle : string option,
                           last : string };
    type name_record =
         {first:string, last:string, middle:string option, student_num:int option}
    
    - fun is_Queen_of_Spades (c : card) =
         #1 c = Spade andalso #2 c = Queen;
    val is_Queen_of_Spades = fn : card -> bool
    
    - val c1 : card = (Diamond, Ace);
    val c1 = (Diamond,Ace) : card
    
    - val c2 : suit * rank = (Heart, Ace);
    val c2 = (Heart,Ace) : suit * rank
    
    - val c3 = (Spade, Ace);
    val c3 = (Spade,Ace) : suit * rank
    
    - is_Queen_of_Spades c1;
    val it = false : bool
    
    - is_Queen_of_Spades c2;
    val it = false : bool
    
    - is_Queen_of_Spades c3;
    val it = false : bool
    
    

    7 Lists和Options也是datatype

    因为datatype可以递归定义,我们可以用来定义自己的lists类型:

    - datatype my_int_list = Empty
                           | Cons of int * my_int_list ;
    = datatype my_int_list = Cons of int * my_int_list | Empty
    
    - val one_two_three = Cons(1, Cons(2, Cons(3, Empty)));
    val one_two_three = Cons (1,Cons (2,Cons #)) : my_int_list
    
    - fun append_mylist (xs, ys) =
        case xs of
            Empty        => ys
          | Cons(x, xs') =>  Cons(x, append_mylist(xs', ys));
    = val append_mylist = fn : my_int_list * my_int_list -> my_int_list
    
    

    对于options, SOMENONE 是构造器。 对于lists来说 []:: 都是构造器。::有点特殊,因为它是中缀操作符(在两个操作数中间)。

    - fun inc_or_zero intoption =
        case intoption of
            NONE => 0
         |  SOME i => i + 1 ;
    = val inc_or_zero = fn : int option -> int
    
    - fun append(xs, ys) =
        case xs of
            [] => ys
         |  x::xs' => x :: append(xs', ys) ;
    = val append = fn : 'a list * 'a list -> 'a list
    

    模式匹配的优点: 不会有例外情况。不能应用错误的函数。

    8 多态数据类型

    好的语言设计:定义自己的多态类型

    datatype 'a option = NONE | SOME of 'a
    
    datatype 'a mylist = Empty | Cons of 'a * 'a mylist
    
    datatype ('a, 'b) tree =
             Node of 'a * ('a, 'b) tree * ('a, 'b) tree
             | Leaf of 'b
    

    9 Each-of类型的模式匹配:val绑定的真相

    val绑定模式,val绑定可以使用模式, val p = e, 例如:

    - val (x,y) = (1,2);
    val x = 1 : int
    val y = 2 : int
    
    - val {f1=a, f2=b} = {f2 =5, f1=3};
    val a = 3 : int
    val b = 5 : int
    

    当知道一个模式肯定会被匹配时,使用模式匹配就是为了提取值。

    - fun full_name (r : {first:string, middle:string, last:string}) =
        let val {first=x, middle=y, last=z} = r
        in
            x ^ " " ^ y ^ " " ^ z
        end
    val full_name = fn : {first:string, last:string, middle:string} -> string
    
    - fun sum_triple (triple : int*int*int) =
        let val (x,y,z) = triple
        in
            x + y + z
        end
    val sum_triple = fn : int * int * int -> int
    
    (* 在定义函数绑定中使用模式 *)
    - fun full_name {first=x, middle=y, last=z} = x ^ " " ^ y ^ " " ^ z
    = val full_name = fn : {first:string, last:string, middle:string} -> string
    
    - full_name {first="a", middle="b", last="c"};
    val it = "a b c" : string
    
    - fun sum_triple (x,y,z) = x + y + z
    = val sum_triple = fn : int * int * int -> int
    

    ML中的所有函数都是一个参数,按照模式匹配展开,可以是tuple:(a,b,c);也可以是record:{a,b,c}或者其它。 这种灵活性很有用,可以把函数的返回值直接传递给其它有多个参数的函数。没有无参数的函数,hello()也是一个参数(空的tuple, unit 类型)。因为存在预定义的类型 , datatype unit = ()

    10 类型推断,多态类型与相等类型

    在ML中,所有的变量和函数都有一个类型,类型推断只是表示不需要把类型写下来。

    类型推断有时会让你的函数更通用。

    多态表示更通用的类型,例如append的类型 'a list * 'a list -> 'a list ,可以统一地把'a替换为 string,就像append具有 string list * string list -> string list 类型一样使用。可以用任何类型替换'a。

    ''a 表示相等类型:

    - fun same_thing(x,y) = if x=y then "yes" else "no";
    stdIn:1.28 Warning: calling polyEqual
    val same_thing = fn : ''a * ''a -> string
    

    11 嵌套模式

    模式是递归的。通常模式匹配就是取一个值和一个模式,然后确定模式是否与值匹配,如果匹配,变量绑定到值的正确部分。模式匹配的递归定义的关键点:

    • 一个变量模式(x)匹配任意值v并引入一个绑定
    • 模式 C 匹配值 C ,如果 C 是一个没有任何数据的构造器
    • 模式 C p (构造器 C 和 模式 p )匹配一个值 C v (注意构造器相同),如果 p 匹配 v (嵌套模式匹配携带的值)。它引入了 p 匹配 v 的绑定。
    • 模式(p1,p2,…,pn)匹配tuple值(v1,v2,…,vn),如果p1匹配v1,p2匹配v2,…,pn匹配vn.它引入所有递归匹配引入的绑定。
    • record模式与tuple类似 {f1=p1,…,fn=pn}

    模式匹配中使用通配符 _ 匹配所有值,但不会引入新绑定。

    - exception BadTriple
    exception BadTriple
    
    - fun zip list_triple =
        case list_triple of
           ([],[],[]) => []
         | (hd1::tl1, hd2::tl2, hd3::tl3) => (hd1,hd2,hd3)::zip(tl1,tl2,tl3)
         | _ => raise BadTriple
    val zip = fn : 'a list * 'b list * 'c list -> ('a * 'b * 'c) list
    
    - fun unzip3 lst =
        case lst of
            [] => ([],[],[])
         |  (a,b,c)::tl => let val (l1,l2,l3) = unzip3 tl
                           in
                               (a::l1, b::l2, c::l3)
                           end
    val unzip3 = fn : ('a * 'b * 'c) list -> 'a list * 'b list * 'c list
    
    - fun nondecreasing intlist =
        case intlist of
            [] => true
         |  _::[] => true
         |  head::(neck::rest) => (head <= neck andalso nondecreasing (neck::rest))
    val nondecreasing = fn : int list -> bool
    
    - datatype sgn = P | N | Z
    = datatype sgn = N | P | Z
    
    - fun multsign (x1,x2) =
        let fun sign x = if x=0 then Z else if x>0 then P else N
        in
            case (sign x1,sign x2) of
                (Z, _) => Z
             |  (_, Z) => Z
             |  (P, P) => P
             |  (N, N) => N
             |  _      => N
        end
    = val multsign = fn : int * int -> sgn
    

    12 异常

    ML有内置的异常概念。使用 raise 抛出一个异常。使用异常绑定创建自己的异常。 异常构造器可以创建 exn 类型的值。

    - exception MyUndesirableCondition
    - exception MyOtherException of int * int
    
    - fun maxlist (xs, ex) =
        case xs of
            [] => raise ex
         |  x::[]  => x
         |  x::xs' => Int.max(x,maxlist(xs',ex));
    val maxlist = fn : int list * exn -> int
    

    处理异常使用handle表达式: e1 handle p => e2 , e1和e2是表达式,p是用来匹配异常的模式。

    13 尾递归和累加器

    新的编程模型:尾递归,编写有效率的递归函数。使用累加器把一些函数变为尾递归。

    - fun sum1 xs =
        case xs of
           [] => 0
         | i::xs' => i + sum1 xs'
    val sum1 = fn : int list -> int
    
    - fun sum2 xs =
        let fun f (xs,acc) =
                case xs of
                    [] => acc
                 |  i::xs' => f(xs',i+acc)
        in
            f(xs,0)
        end
    val sum2 = fn : int list -> int
    

    函数调用的实现依靠调用栈(call stack),调用栈的内容是每个函数为一个元素,这个函数是已启动但还没有完毕的调用。 每个元素保存局部变量和函数还未求值的部分。当一个函数体内调用了另一个函数,一个新的元素push到调用栈,当被调用的函数完成后弹出。

    对于 sum1 ,每个 sum1 递归调用都会产生一个调用栈元素,栈会和列表一样大。因为在弹出每个栈帧后,调用者要加 i 到每个递归结果并返回。

    对于 sum2 ,在被调用函数返回后,调用函数不需要做任何事,只需返回被调用函数的结果。这种情况叫做尾递归,函数式语言通常会对这种情况优化:当调用一个尾递归调用,调用者的栈帧在调用之前弹出,被调用者的栈帧替换调用者的。这很简单:调用者只是返回被调用者的结果。因此,调用 sum2 只需要1个栈帧。

    使用累加器是把递归函数转换为尾递归函数的常用方法。通常转换一个非尾递归函数到尾递归函数需要满足结合律。

    如果一个调用在尾部位置,它就是尾递归调用。尾部位置定义如下:

    • fun f(x) = e 中, e 在尾部位置
    • 如果一个表达式不在尾部位置,那么它的子表达式都不在尾部位置
    • 如果 if e1 then e2 else e3 在尾部位置,则 e2e3 在尾部位置( e1 不在),case表达式类似。
    • 如果 let b1 … bn in e end 在尾部位置, 则 e 在尾部位置(但绑定中的表达式不在)。
    • 函数调用参数不在尾部位置。

    作者: ntestoc

    Created: 2018-12-12 Wed 13:44

  • 相关阅读:
    编程实现SQL Server数据库导入导出操作
    C#正则表达式入门
    Winform实现窗体抖动的效果代码
    js获取当前日期,格式为YYYYMMDD
    XML基本知识及其技术指南
    WPF学习心得
    脚本调试
    标题:VS2008简体中文专业版
    使用 XML Schema 定义元素的基本知识2
    C#操作xml文件入门
  • 原文地址:https://www.cnblogs.com/ntestoc/p/10052207.html
Copyright © 2011-2022 走看看