• OCaml入门(5)


    匿名函数

    函数式语言中,函数是一等公民,它应该可以像其它类型(int,float,string等)一样,能支持“字面量”,这就是匿名函数。

    utop # (fun x -> x + 1);;
    - : int -> int = <fun> 
    utop # (fun x -> x + 1) 7;;
    - : int = 8 

    可以看到,匿名函数可以代替函数的位置来使用。

    有些函数需要的参数是另一个函数,当然也可以用匿名函数传入。比如:

    utop # List.map ~f:(fun x->x+1) [1;2;3];;
    - : int list = [2; 3; 4]

    不要太在意: ~f: 这样的语法,这只是具名函数调用的用法。大多数语言中,函数的调用都是按位置来匹配参数的。

    这样做的缺点是容易忘记哪个参数在哪个位置。Ocaml当然也支持按位置传参调用函数的方式,但同时鼓励按名字传参来调用函数,这样的程序更具可读性。

    (你可能会觉得这多别扭啊,暂且忍受,人们往往都是习惯的奴隶)

    匿名函数当然能绑定到一个符号,这样就成了具名函数:

    utop # let plusone = fun x -> x + 1;;
    val plusone : int -> int = <fun>    
    utop # plusone 5;;
    - : int = 6   

    这种定义如此普遍,Ocaml提供了语法糖来简化:

    utop # let plusone x = x + 1;;
    val plusone : int -> int = <fun>  

    这与上一个定义是完全一样的,只是语法偷懒了而已。

    匿名函数也可以有多个参数:

    utop # let diff x y = abs (x - y);;
    val diff : int -> int -> int = <fun>  
    utop # diff 5 8;;
    - : int = 3   

    你可能会很奇怪这个函数的类型签名: int -> int -> int

    在我们的想象中,应该是:(int, int)-> int 才更合理啊。

    这正是函数式语言更数学化的特征之一。在数学上,严格地说,每个函数都只能传入一个参数,并且返回一个参数。

    如果有一个函数看起来传入两个参数,它只不过是传入一个参数又返回了另一个函数的函数。

    也就是说,本质上,上边的函数定义等价于:

    utop # let diff = 
    fun x -> (fun y -> abs (x - y));;
    val diff : int -> int -> int = <fun>  

    这是个脑力体操,仔细想想吧。

    函数式语言的威力恰在于:函数可以作为参数,函数可以作为返回值。

    按照这个思路,我们顺理成章地可以这样写:

    utop # let diff3 = diff 3;;
    val diff3 : int -> int = <fun>  
    utop # diff3 8;;
    - : int = 5     

    对于多参的函数,只提供部分参数的做法很普遍,数学上叫做:偏参函数。

    函数的递归

    函数式语言不鼓励(甚至是不允许)使用变量 + 循环的编程模式,比起循环来,它更青睐于“递归”。

    递归就是一函数直接或间接地调用自己。

    utop # let rec len lst = match lst with
    | [] -> 0
    | h::tail -> 1 + len tail;; 
    val len : 'a list -> int = <fun>    
    utop # len [1;2;3;4];;
    - : int = 4      
    utop # len [];;
    - : int = 0  

    如果一函数是递归的,Ocaml要求必须明确地用 rec来修饰。

    rec是recursive的缩写。

    如果间接递归就需要一次把多个函数都定义出来,比如: is_even 来判断是否为偶数。这里只是为了示范概念,当然仅仅为这个小功能没必要这样大费周章。

    utop # let rec is_even x =
    if x=0 then true else is_odd (x-1)
    and is_odd x =
    if x=0 then false else is_even (x-1);;
    val is_even : int -> bool = <fun>
    val is_odd : int -> bool = <fun>       
    utop # is_even 7;;
    - : bool = false  
    utop # is_even 8;;
    - : bool = true 

    前缀与中缀

    一般的函数调用都是前缀格式: 函数 参数 参数 ...

    有的时候,两个参数时,中缀更符合习惯。比如: 1 + 2,   5 mod 3

    Ocaml中,函数与运算符实在同样的东西,都是: function

    中缀运算符如果加上括号,就回到了前缀的风格:

    utop # (+) 5 8;;
    - : int = 13 
    utop # (mod) 10 3;;
    - : int = 1  
    utop # List.map ~f:((+) 3) [1;2;3;4];;
    - : int list = [4; 5; 6; 7]    

    自己定义中缀运算符也可以,必须从如下符号集合中组合:

    ! $ % & * + - . / : < = > ? @ ^ | ~
    比如:
    utop # let (+!) (x1,y1) (x2,y2) = x1+x2, y1+y2;;
    val ( +! ) : int * int -> int * int -> int * int = <fun> 
    utop # (1,2) +! (10,20);;
    - : int * int = (11, 22)

    使用含有 * 的运算符需要格外小心,因为 (* 。。。。 *) 表示注释语句。

    utop # ( * ) 3 5;;
    - : int = 15  

    这里括号与星号之间的空格是必须的!

    Function

    let ff x = match x with ... | ... | ...

    这种模式如此普遍,OCaml提供了进一步的简化语法:

    utop # let len = function
    | [] -> 0
    | h::t -> 1 + len t;;
    val len : 'a list -> int = <fun>     
    utop # len [1;2];;
    - : int = 2  

    标签参数

    多参数函数可以使用标签参数来增加可读性和灵活性。

    下面的函数求坡度。

    utop # let po ~path ~height = let pi = atan 1.0 *. 4. in
    (asin (height /. path)) *. 180.0 /. pi;;
    val po : path:float -> height:float -> float = <fun> 
    utop # po ~path:10. ~height:1.;;
    - : float = 5.73917047727  

    既然参数有名字,顺序就无关紧要了。

    utop # po ~height:1. ~path:10.;;
    - : float = 5.73917047727  

     可选参数

    可选参数就是可能出现,也可能省略的参数。在OCaml类型中表现为Option类型。

    utop # let concat ?sep x y = 
    let s = match sep with None->"" | Some x -> x in
    x ^ s ^ y;;
    val concat : ?sep:string -> string -> string -> string = <fun>   
    utop # concat "dog" "cat";;
    - : string = "dogcat"     
    utop # concat "dog" "cat" ~sep:",";;
    - : string = "dog,cat" 

    对于Option类型的解构太常用了,所以Ocaml进一步提供了简便语法:

    utop # let concat ?(sep="") x y = x ^ sep ^ y;;
    val concat : ?sep:string -> string -> string -> string = <fun>   

    含义与前边的定义完全相同。

  • 相关阅读:
    linux下文件夹的创建、复制、剪切、重命名、清空和删除命令
    Linux 删除文件夹和创建文件的命令
    linux下拷贝整个目录
    星云大师:这十句话 我受用一生
    dex
    瘋耔java语言笔记
    VCC_VID_VTT等的含义
    一位数码管引脚
    android从应用到驱动之—camera(2)---cameraHAL的实现
    android从应用到驱动之—camera(1)---程序调用流程[转]
  • 原文地址:https://www.cnblogs.com/gyhang/p/4785193.html
Copyright © 2020-2023  润新知