Ocaml 学习笔记

之前研究Mina项目,学习下Ocaml语法。

A Tour

Base 提供了基本的标准库,Core 对 Base 进行了扩展。

open Base;;

Type Inference:

let sum_if_true test first second =
  (if test first then first else 0)
  + (if test second then second else 0);;
val sum_if_true : (int -> bool) -> int -> int -> int = 

Inferring generic types

let first_if_true test x y =
  if test x then x else y;;
val first_if_true : ('a -> bool) -> 'a -> 'a -> 'a = 

Tuples:

let a_tuple = (3,"three");;
val a_tuple : int * string = (3, "three")
let another_tuple = (3,"four",5.);;
val another_tuple : int * string * float = (3, "four", 5.)

let (x,y) = a_tuple;;
val x : int = 3
val y : string = "three"

Lists:

let languages = ["OCaml";"Perl";"C"];;
val languages : string list = ["OCaml"; "Perl"; "C"]

List.length languages;;
- : int = 3

List.map languages ~f:String.length;;
- : int list = [5; 4; 1]

Constructing Lists with :::

"French" :: "Spanish" :: languages;;
- : string list = ["French"; "Spanish"; "OCaml"; "Perl"; "C"]

languages;;
- : string list = ["OCaml"; "Perl"; "C"]

Recursive List Functions

let rec sum l =
  match l with
  | [] -> 0                   (* base case *)
  | hd :: tl -> hd + sum tl   (* inductive case *);;
val sum : int list -> int = 
sum [1;2;3];;
- : int = 6

Options

let divide x y =
  if y = 0 then None else Some (x / y);;
val divide : int -> int -> int option = 
let downcase_extension filename =
  match String.rsplit2 filename ~on:'.' with
  | None -> filename
  | Some (base,ext) ->
    base ^ "." ^ String.lowercase ext;;
val downcase_extension : string -> string = 
List.map ~f:downcase_extension
  [ "Hello_World.TXT"; "Hello_World.txt"; "Hello_World" ];;
- : string list = ["Hello_World.txt"; "Hello_World.txt"; "Hello_World"]

^ 用于连接字符串。

Records and Variants

type point2d = { x : float; y : float }
let p = { x = 3.; y = -4. };;
val p : point2d = {x = 3.; y = -4.}
let magnitude { x = x_pos; y = y_pos } =
  Float.sqrt (x_pos **. 2. +. y_pos **. 2.);;
val magnitude : point2d -> float = 

field punning

let magnitude { x; y } = Float.sqrt (x **. 2. +. y **. 2.);;
val magnitude : point2d -> float = 

Variant types

type circle_desc  = { center: point2d; radius: float }
type rect_desc    = { lower_left: point2d; width: float; height: float }
type segment_desc = { endpoint1: point2d; endpoint2: point2d }

type scene_element =
  | Circle  of circle_desc
  | Rect    of rect_desc
  | Segment of segment_desc

Imperative Programming

Arrays:

let numbers = [| 1; 2; 3; 4 |];;
val numbers : int array = [|1; 2; 3; 4|]
numbers.(2) <- 4;;
- : unit = ()
numbers;;
- : int array = [|1; 2; 4; 4|]

Mutable Record Fields

type running_sum =
  { mutable sum: float;
    mutable sum_sq: float; (* sum of squares *)
    mutable samples: int;
  }

Refs

let x = { contents = 0 };;
val x : int ref = {contents = 0}
x.contents <- x.contents + 1;;
- : unit = ()
x;;
- : int ref = {contents = 1}
let x = ref 0  (* create a ref, i.e., { contents = 0 } *);;
val x : int ref = {Base.Ref.contents = 0}
!x             (* get the contents of a ref, i.e., x.contents *);;
- : int = 0
x := !x + 1    (* assignment, i.e., x.contents <- ... *);;
- : unit = ()
!x;;
- : int = 1

For and While Loops

let permute array =
  let length = Array.length array in
  for i = 0 to length - 2 do
    (* pick a j to swap with *)
    let j = i + Random.int (length - i) in
    (* Swap i and j *)
    let tmp = array.(i) in
    array.(i) <- array.(j);
    array.(j) <- tmp
  done;;
val permute : 'a array -> unit = 
let find_first_negative_entry array =
  let pos = ref 0 in
  while !pos < Array.length array && array.(!pos) >= 0 do
    pos := !pos + 1
  done;
  if !pos = Array.length array then None else Some !pos;;
val find_first_negative_entry : int array -> int option = 
find_first_negative_entry [|1;2;0;3|];;
- : int option = None
find_first_negative_entry [|1;-2;0;3|];;
- : int option = Some 1

Variables and Functions

Variables

let  = 

let  =  in 

Pattern Matching and Let

let (ints,strings) = List.unzip [(1,"one"); (2,"two"); (3,"three")];;
val ints : int list = [1; 2; 3]
val strings : string list = ["one"; "two"; "three"]

Anonymous Functions

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

在Ocaml中,函数只是普通的值,可以执行各种操作。

let plusone = (fun x -> x + 1);;
val plusone : int -> int = 
plusone 3;;
- : int = 4

等价于:

let plusone x = x + 1;;
val plusone : int -> int = 

function

可以采用 function 声明函数:

let some_or_zero = function
  | Some x -> x
  | None -> 0;;
val some_or_zero : int option -> int = 
List.map ~f:some_or_zero [Some 3; None; Some 4];;
- : int list = [3; 0; 4]

与下述代码等价:

let some_or_zero num_opt =
  match num_opt with
  | Some x -> x
  | None -> 0;;
val some_or_zero : int option -> int = 

多变量函数

let abs_diff x y = abs (x - y);;
val abs_diff : int -> int -> int = 
abs_diff 3 4;;
- : int = 1

Curry function:

let abs_diff =
(fun x -> (fun y -> abs (x - y)));;
val abs_diff : int -> int -> int = 
val abs_diff : int -> (int -> int)

元组

let abs_diff (x,y) = abs (x - y);;
val abs_diff : int * int -> int = 
abs_diff (3,4);;
- : int = 1

Recursive Functions

let rec find_first_repeat list =
  match list with
  | [] | [_] ->
    (* only zero or one elements, so no repeats *)
    None
  | x :: y :: tl ->
    if x = y then Some x else find_first_repeat (y::tl);;
val find_first_repeat : int list -> int option = 

相互递归的函数:

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 = 
val is_odd : int -> bool = 
List.map ~f:is_even [0;1;2;3;4;5];;
- : bool list = [true; false; true; false; true; false]
List.map ~f:is_odd [0;1;2;3;4;5];;
- : bool list = [false; true; false; true; false; true]

Prefix and Infix Operators

Int.max 3 4  (* prefix *);;
- : int = 4
3 + 4        (* infix  *);;
- : int = 7

(+) 3 4;;
- : int = 7

Reverse application operator

let (|>) x f = f x;;
val ( |> ) : 'a -> ('a -> 'b) -> 'b = 

应用操作符

|> 是反向操作符, 也有一个应用操作符。

(@@);;
- : ('a -> 'b) -> 'a -> 'b = 

带标签的参数

let ratio ~num ~denom = Float.of_int num /. Float.of_int denom;;
val ratio : num:int -> denom:int -> float = 

调用:

ratio ~num:3 ~denom:10;;
- : float = 0.3
ratio ~denom:10 ~num:3;;
- : float = 0.3

Lable prunning:

let num = 3 in
let denom = 4 in
ratio ~num ~denom;;
- : float = 0.75

可选参数

采用 ? 标记:

let concat ?sep x y =
  let sep = match sep with None -> "" | Some s -> s in
  x ^ sep ^ y;;
val concat : ?sep:string -> string -> string -> string = 
concat "foo" "bar"             (* without the optional argument *);;
- : string = "foobar"
concat ~sep:":" "foo" "bar"    (* with the optional argument    *);;
- : string = "foo:bar"

Lists and Patterns

基础

open Base;;
[1;2;3];;
- : int list = [1; 2; 3]

采用 :: 符号:

1 :: (2 :: (3 :: []));;
- : int list = [1; 2; 3]
1 :: 2 :: 3 :: [];;
- : int list = [1; 2; 3]

空列表[] 是多态的:

let empty = [];;
val empty : 'a list = []
3 :: empty;;
- : int list = [3]
"three" :: empty;;
- : string list = ["three"]

List 模式匹配

let rec sum l =
  match l with
  | [] -> 0
  | hd :: tl -> hd + sum tl;;
val sum : int list -> int = 
sum [1;2;3];;
- : int = 6
sum [];;
- : int = 0

benchmark

模式匹配比其它实现方式速度要快。

let rec sum_if l =
  if List.is_empty l then 0
  else List.hd_exn l + sum_if (List.tl_exn l);;
val sum_if : int list -> int = 

速度测试:

let numbers = List.range 0 1000 in
[ Bench.Test.create ~name:"sum_if" (fun () -> sum_if numbers)
; Bench.Test.create ~name:"sum"    (fun () -> sum numbers) ]
|> Bench.bench;;
Estimated testing time 20s (2 benchmarks x 10s). Change using -quota SECS.
┌────────┬──────────┐
│ Name   │ Time/Run │
├────────┼──────────┤
│ sum_if │  62.00us │
│ sum    │  17.99us │
└────────┴──────────┘
- : unit = ()

List 模块

List.map ~f:String.length ["Hello"; "World!"];;
- : int list = [5; 6]
List.map2_exn ~f:Int.max [1;2;3] [3;2;1];;
- : int list = [3; 2; 3]
List.fold ~init:0 ~f:(+) [1;2;3;4];;
- : int = 10

String.concat^ 的性能比较

let s = "." ^ "."  ^ "."  ^ "."  ^ "."  ^ "."  ^ ".";;
val s : string = "......."
let s = String.concat [".";".";".";".";".";".";"."];;
val s : string = "......."

String.concat 函数更高效,^ 需要每次分配一个新的字符串。

List.reduce

List.reduce;;
- : 'a list -> f:('a -> 'a -> 'a) -> 'a option = 

List.filter

List.filter ~f:(fun x -> x % 2 = 0) [1;2;3;4;5];;
- : int list = [2; 4]

List.filter_map

let extensions filenames =
  List.filter_map filenames ~f:(fun fname ->
      match String.rsplit2 ~on:'.' fname with
      | None  | Some ("",_) -> None
      | Some (_,ext) ->
        Some ext)
  |> List.dedup_and_sort ~compare:String.compare;;
val extensions : string list -> string list = 
extensions ["foo.c"; "foo.ml"; "bar.ml"; "bar.mli"];;
- : string list = ["c"; "ml"; "mli"]

List.partition_tf:

let is_ocaml_source s =
  match String.rsplit2 s ~on:'.' with
  | Some (_,("ml"|"mli")) -> true
  | _ -> false;;
val is_ocaml_source : string -> bool = 
let (ml_files,other_files) =
List.partition_tf ["foo.c"; "foo.ml"; "bar.ml"; "bar.mli"]  ~f:is_ocaml_source;;
val ml_files : string list = ["foo.ml"; "bar.ml"; "bar.mli"]
val other_files : string list = ["foo.c"]

List.append

List.append [1;2;3] [4;5;6];;
- : int list = [1; 2; 3; 4; 5; 6]

@ 和 List.append 等价。

[1;2;3] @ [4;5;6];;
- : int list = [1; 2; 3; 4; 5; 6]

List.concat:

List.concat [[1;2];[3;4;5];[6];[]];;
- : int list = [1; 2; 3; 4; 5; 6]

Tail recursion

Non-tail recursion

let rec length = function
  | [] -> 0
  | _ :: tl -> 1 + length tl;;
val length : 'a list -> int = 
length [1;2;3];;
- : int = 3

Tail recursion

let rec length_plus_n l n =
  match l with
  | [] -> n
  | _ :: tl -> length_plus_n tl (n + 1);;
val length_plus_n : 'a list -> int -> int = 
let length l = length_plus_n l 0;;
val length : 'a list -> int = 
length [1;2;3;4];;
- : int = 4

只有最终的结果返回给调用者。

Terser and Faster patterns

优化前:

let rec remove_sequential_duplicates list =
  match list with
  | [] -> []
  | [x] -> [x]
  | first :: second :: tl ->
    if first = second then
      remove_sequential_duplicates (second :: tl)
    else
      first :: remove_sequential_duplicates (second :: tl);;
val remove_sequential_duplicates : int list -> int list = 

优化后:

let rec remove_sequential_duplicates list =
  match list with
  | [] as l -> l
  | [_] as l -> l
  | first :: (second :: _ as tl) ->
    if first = second then
      remove_sequential_duplicates tl
    else
      first :: remove_sequential_duplicates tl;;
val remove_sequential_duplicates : int list -> int list = 

进一步优化;

let rec remove_sequential_duplicates list =
  match list with
  | [] | [_] as l -> l
  | first :: (second :: _ as tl) ->
    if first = second then
      remove_sequential_duplicates tl
    else
      first :: remove_sequential_duplicates tl;;
val remove_sequential_duplicates : int list -> int list = 

采用when

let rec remove_sequential_duplicates list =
  match list with
  | [] | [_] as l -> l
  | first :: (second :: _ as tl) when first = second ->
    remove_sequential_duplicates tl
  | first :: tl -> first :: remove_sequential_duplicates tl;;
val remove_sequential_duplicates : int list -> int list = 

Polymorphic compare

Base.Poly

open Base.Poly;;
"foo" = "bar";;
- : bool = false
3 = 4;;
- : bool = false
[1;2;3] = [1;2;3];;
- : bool = true
let rec remove_sequential_duplicates list =
  match list with
  | [] | [_] as l -> l
  | first :: (second :: _ as tl) when first = second ->
    remove_sequential_duplicates tl
  | first :: tl -> first :: remove_sequential_duplicates tl;;
val remove_sequential_duplicates : 'a list -> 'a list = 
remove_sequential_duplicates [1;2;2;3;4;3;3];;
- : int list = [1; 2; 3; 4; 3]
remove_sequential_duplicates ["one";"two";"two";"two";"three"];;
- : string list = ["one"; "two"; "three"]

List.count

let count_some l = List.count ~f:Option.is_some l;;
val count_some : 'a option list -> int = 

Files, Modules and Programs

freq.ml

open Base
open Stdio

let build_counts () =
  In_channel.fold_lines In_channel.stdin ~init:[] ~f:(fun counts line ->
      let count =
        match List.Assoc.find ~equal:String.equal counts line with
        | None -> 0
        | Some x -> x
      in
      List.Assoc.add ~equal:String.equal counts line (count + 1))

let () =
  build_counts ()
  |> List.sort ~compare:(fun (_, x) (_, y) -> Int.descending x y)
  |> (fun l -> List.take l 10)
  |> List.iter ~f:(fun (line, count) -> printf "%3d: %s\n" count line)

main 函数

let () = 

运行:

ocamlopt freq.ml -o freq
ocamlfind ocamlopt -linkpkg -package base -package stdio freq.ml -o freq 

dune-project: 一个项目一个构建文件;

dune: 一个文件夹一个

dune build freq.exe
./_build/default/freq.exe

dune exec ./freq.exe

ocmalc: bytecode compiler .bc

ocamlopt: nativecode compiler .exe

模块首字母大写。

模块接口和实现

具体类型和抽象类型

嵌入式模块

module  :  = 

Include: 包含其它模块的内容

open Base

(* The new function we're going to add *)
let apply f_opt x =
  match f_opt with
  | None -> None
  | Some f -> Some (f x)

(* The remainder of the option module *)
include Option

module type of :

open Base

(* Include the interface of the option module from Base *)
include module type of Option

(* Signature of function we're adding *)
val apply : ('a -> 'b) t -> 'a -> 'b t

Records

basic syntax:

type  =
    {  : ;
       : ;
      ...
    }

Polymorphic type:

type 'a with_line_num = { item: 'a; line_num: int }

Patterns and Exhaustiveness

let service_info_to_string
  { service_name = name; port = port; protocol = prot  }
  =
  sprintf "%s %i/%s" name port prot
;;
val service_info_to_string : service_info -> string = 
service_info_to_string ssh;;
- : string = "ssh 22/udp"

Records patterns are irrefutable.

#warnings "+9";;
let service_info_to_string
  { service_name = name; port = port; protocol = prot  }
  =
  sprintf "%s %i/%s" name port prot
;;
Line 2, characters 5-59:
Warning 9 [missing-record-field-pattern]: the following labels are not bound in this record pattern:
comment
Either bind these labels explicitly or add '; _' to the pattern.
val service_info_to_string : service_info -> string = 

Field Punning

let service_info_to_string { service_name; port; protocol; comment } =
  let base = sprintf "%s %i/%s" service_name port protocol in
  match comment with
  | None -> base
  | Some text -> base ^ " #" ^ text;;
val service_info_to_string : service_info -> string = 

Reusing Field Names

添加type annotations

let get_heartbeat_session_id (t:heartbeat) = t.session_id;;
val get_heartbeat_session_id : heartbeat -> string = 
let message_to_string { Log_entry.important; message; _ } =
  if important then String.uppercase message else message;;
val message_to_string : Log_entry.t -> string = 
let is_important t = t.Log_entry.important;;
val is_important : Log_entry.t -> bool = 

Functional Updates

仅需更新一个字段:

let register_heartbeat t hb =
  { t with last_heartbeat_time = hb.Heartbeat.time };;
val register_heartbeat : client_info -> Heartbeat.t -> client_info = 

Mutable Fields:

type client_info =
  { addr: Unix.Inet_addr.t;
    port: int;
    user: string;
    credentials: string;
    mutable last_heartbeat_time: Time_ns.t;
    mutable last_heartbeat_status: string;
  }
let register_heartbeat t (hb:Heartbeat.t) =
  t.last_heartbeat_time   <- hb.time;
  t.last_heartbeat_status <- hb.status_message;;
val register_heartbeat : client_info -> Heartbeat.t -> unit = 

First-Class Fields

[@@deriving fields] annotation

#require "ppx_jane";;
module Logon = struct
  type t =
    { session_id: string;
      time: Time_ns.t;
      user: string;
      credentials: string;
    }
  [@@deriving fields]
end;;
module Logon :
  sig
    type t = {
      session_id : string;
      time : Time_ns.t;
      user : string;
      credentials : string;
    }
    val credentials : t -> string
    val user : t -> string
    val time : t -> Time_ns.t
    val session_id : t -> string
    module Fields :
      sig
        val names : string list
        val credentials :
          ([< `Read | `Set_and_create ], t, string) Field.t_with_perm
        val user :
          ([< `Read | `Set_and_create ], t, string) Field.t_with_perm
        val time :
          ([< `Read | `Set_and_create ], t, Time_ns.t) Field.t_with_perm
...
      end
  end

Variants

variant type

type  =
  |  [ of  [* ]... ]
  |  [ of  [* ]... ]
  | ...
open Base
open Stdio
type basic_color =
  | Black | Red | Green | Yellow | Blue | Magenta | Cyan | White
let basic_color_to_int = function
  | Black -> 0 | Red     -> 1 | Green -> 2 | Yellow -> 3
  | Blue  -> 4 | Magenta -> 5 | Cyan  -> 6 | White  -> 7;;
val basic_color_to_int : basic_color -> int = 
List.map ~f:basic_color_to_int [Blue;Red];;
- : int list = [4; 1]
type weight = Regular | Bold
type color =
  | Basic of basic_color * weight (* basic colors, regular and bold *)
  | RGB   of int * int * int      (* 6x6x6 color cube *)
  | Gray  of int     

Variants, Tuples, Parens

Testing

Inline Tests

(library
 (name foo)
 (libraries base stdio)
 (inline_tests)
 (preprocess (pps ppx_inline_test)))
open Base

let%test "rev" =
  List.equal Int.equal (List.rev [ 3; 2; 1 ]) [ 1; 2; 3 ]
dune runtest

使用let%test_unit[%test_eq]

(library
 (name foo)
 (libraries base stdio)
 (preprocess
  (pps ppx_inline_test ppx_assert))
 (inline_tests))
open Base

let%test_unit "rev" =
  [%test_eq: int list] (List.rev [ 3; 2; 1 ]) [ 3; 2; 1 ]

Expect tests:

open! Base
open Stdio

let%expect_test "trivial" = print_endline "Hello World!"

open!: 压缩warning

open Base
open Stdio

let%expect_test _ =
  print_s [%sexp (List.rev [ 3; 2; 1 ] : int list)];
  [%expect {| (1 2 3) |}]
open Base

let%test "rev" =
  List.equal Int.equal (List.rev [ 3; 2; 1 ]) [ 1; 2; 3 ]

Quoted string:

{|This is a quoted string|};;
- : string = "This is a quoted string"

Property Testing with Quickcheck

open Core

let%test_unit "List.rev_append is List.append of List.rev" =
  Quickcheck.test
    ~sexp_of:[%sexp_of: int list * int list]
    [%quickcheck.generator: int list * int list]
    ~f:(fun (l1, l2) ->
      [%test_eq: int list]
        (List.rev_append l1 l2)
        (List.append (List.rev l1) l2

参考

https://dev.realworldocaml.org/

https://v3.ocaml.org/packages

你可能感兴趣的:(Ocaml 学习笔记)