Spec-Zone.ru › OCaml
☰Язык OCaml
  • Язык OCaml
  • Расширения языка

Глава 11 Язык OCaml

7 Выражения

  • 7.1 Приоритет и ассоциативность
  • 7.2 Базовые выражения
  • 7.3 Конструкции управления
  • 7.4 Операции над структурами данных
  • 7.5 Операторы
  • 7.6 Объекты
  • 7.7 Приведения типов
  • 7.8 Другое
expr ::= value-path
∣ constant
∣ ( expr )
∣ begin expr end
∣ ( expr : typexpr )
∣ expr { , expr }+
∣ constr expr
∣ `tag-name expr
∣ expr :: expr
∣ [ expr { ; expr } [;] ]
∣ [| expr { ; expr } [;] |]
∣ { field [: typexpr] [= expr]{ ; field [: typexpr] [= expr] } [;] }
∣ { expr with field [: typexpr] [= expr]{ ; field [: typexpr] [= expr] } [;] }
∣ expr { argument }+
∣ prefix-symbol expr
∣ - expr
∣ -. expr
∣ expr infix-op expr
∣ expr . field
∣ expr . field <- expr
∣ expr .( expr )
∣ expr .( expr ) <- expr
∣ expr .[ expr ]
∣ expr .[ expr ] <- expr
∣ if expr then expr [ else expr ]
∣ while expr do expr done
∣ for value-name = expr ( to ∣ downto ) expr do expr done
∣ expr ; expr
∣ match expr with pattern-matching
∣ function pattern-matching
∣ fun { parameter }+ [ : typexpr ] -> expr
∣ try expr with pattern-matching
∣ let [rec] let-binding { and let-binding } in expr
∣ let exception constr-decl in expr
∣ let module module-name { ( module-name : module-type ) } [ : module-type ] = module-expr in expr
∣ ( expr :> typexpr )
∣ ( expr : typexpr :> typexpr )
∣ assert expr
∣ lazy expr
∣ local-open
∣ object-expr
argument ::= expr
∣ ~ label-name
∣ ~ label-name : expr
∣ ? label-name
∣ ? label-name : expr
pattern-matching ::= [ | ] pattern [when expr] -> expr { | pattern [when expr] -> expr }
let-binding ::= pattern = expr
∣ value-name { parameter } [: typexpr] [:> typexpr] = expr
∣ value-name : poly-typexpr = expr
parameter ::= pattern
∣ ~ label-name
∣ ~ ( label-name [: typexpr] )
∣ ~ label-name : pattern
∣ ? label-name
∣ ? ( label-name [: typexpr] [= expr] )
∣ ? label-name : pattern
∣ ? label-name : ( pattern [: typexpr] [= expr] )
local-open ::=
∣ let open module-path in expr
∣ module-path .( expr )
∣ module-path .[ expr ]
∣ module-path .[| expr |]
∣ module-path .{ expr }
∣ module-path .{< expr >}
object-expr ::=
∣ new class-path
∣ object class-body end
∣ expr # method-name
∣ inst-var-name
∣ inst-var-name <- expr
∣ {< [ inst-var-name [= expr] { ; inst-var-name [= expr] } [;] ] >}

См. также следующие расширения языка: модули первого класса, переопределение в операторах open, синтаксис для доступа к Bigarray, атрибуты, узлы расширения и расширенные операторы индексирования.

7.1 Приоритет и ассоциативность

Таблица ниже показывает относительные приоритеты и ассоциативность операторов и незамкнутых конструкций. Конструкции с более высоким приоритетом идут первыми. Для инфиксных и префиксных символов мы пишем «*…», чтобы обозначить «любой символ, начинающийся с *».

Конструкция или оператор Ассоциативность
prefix-symbol –
. .( .[ .{ (см. раздел ‍12.11) –
#… left
применение функции, применение конструктора, применение тега, assert, lazy left
- -. (префикс) –
**… lsl lsr asr right
*… /… %… mod land lor lxor left
+… -… left
:: right
@… ^… right
=… <… >… |… &… $… != left
& && right
or || right
, –
<- := right
if –
; right
let match fun function try –

Проверить или освежить свои знания очень просто:

# 3 + 3 mod 2, 3 + (3 mod 2), (3 + 3) mod 2;;

- : int * int * int = (4, 4, 0)

7.2 Базовые выражения

Константы

Выражение, состоящее из константы, вычисляется в эту константу. Например, 3.14 или [||].

Пути к значениям

Выражение, состоящее из пути доступа, вычисляется в значение, привязанное к этому пути в текущей среде выполнения. Путь может быть именем значения или путем доступа к компоненту значения модуля.

# Float.ArrayLabels.to_list;;

- : Float.ArrayLabels.t -> float list = 

Выражения в скобках

Выражения ( expr ) и begin expr end имеют то же значение, что и expr. Две конструкции семантически эквивалентны, но рекомендуется использовать begin … end внутри управляющих структур:

        if … then begin … ; … end else begin … ; … end

а ( … ) для других ситуаций группировки.

# let x = 1 + 2 * 3
  let y = (1 + 2) * 3;;

val x : int = 7
val y : int = 9
# let f a b =
    if a = b then
      print_endline "Equal"
    else begin
        print_string "Not Equal: ";
        print_int a;
        print_string " and ";
        print_int b;
        print_newline ()
    end;;

val f : int -> int -> unit = 

Выражения в скобках могут содержать ограничение типа, как в ( expr : typexpr ). Это ограничение заставляет тип expr быть совместимым с typexpr.

Выражения в скобках также могут содержать приведения типов ( expr [: typexpr] :> typexpr) (см. подраздел ‍11.7.7 ниже).

Применение функции

Применение функции обозначается примыканием (возможно, именованных) выражений. Выражение expr argument1 … argumentn вычисляет выражение expr и те, которые появляются в argument1 до argumentn. Выражение expr должно вычисляться в функциональное значение f, которое затем применяется к значениям argument1, …, argumentn.

Порядок, в котором вычисляются выражения expr, argument1, …, argumentn, не указан.

# List.fold_left ( + ) 0 [1; 2; 3; 4; 5];;

- : int = 15

Аргументы и параметры сопоставляются в соответствии с их метками. Порядок аргументов не имеет значения, за исключением аргументов с одной и той же меткой или без метки.

# ListLabels.fold_left ~f:( @ ) ~init:[] [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]];;

- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]

Если параметр указан как необязательный (метка, опережаемая ?) в типе expr, соответствующий аргумент будет автоматически обернут конструктором Some, если только сам аргумент также не опережается ?, в этом случае он передается как есть.

# let fullname ?title first second =
    match title with
    | Some t -> t ^ " " ^ first ^ " " ^ second
    | None -> first ^ " " ^ second

  let name = fullname ~title:"Mrs" "Jane" "Fisher"

  let address ?title first second town =
    fullname ?title first second ^ "\n" ^ town;;

val fullname : ?title:string -> string -> string -> string = 
val name : string = "Mrs Jane Fisher"
val address : ?title:string -> string -> string -> string -> string = 

Если передается неименованный аргумент, а его соответствующий параметр предшествует одному или нескольким необязательным параметрам, то эти параметры принимают значения по умолчанию, т.е. для них будет передано значение None. Все остальные отсутствующие параметры (без соответствующего аргумента), как необязательные, так и обязательные, будут сохранены, и результатом функции по-прежнему будет функция этих отсутствующих параметров к телу f.

# let fullname ?title first second =
    match title with
    | Some t -> t ^ " " ^ first ^ " " ^ second
    | None -> first ^ " " ^ second

  let name = fullname "Jane" "Fisher";;

val fullname : ?title:string -> string -> string -> string = 
val name : string = "Jane Fisher"

Во всех случаях, кроме точного совпадения порядка и меток, без необязательных параметров, тип функции должен быть известен в точке применения. Это можно обеспечить, добавив ограничение типа. Принципиальность вывода может быть проверена в режиме -principal.

В качестве частного случая OCaml поддерживает полные применения с labels-omitted: если функция имеет известную арность, все аргументы неименованные, и их количество совпадает с количеством обязательных параметров, то метки игнорируются, и обязательные параметры сопоставляются в порядке их определения. Необязательные аргументы принимают значения по умолчанию. Это пропуск меток не рекомендуется и приводит к предупреждению, см. 13.5.1.

Определение функции

Предусмотрено две синтаксические формы для определения функций. Первая форма вводится ключевым словом function:

function pattern1 -> expr1
| …
| patternn -> exprn

Это выражение вычисляется в функциональное значение с одним аргументом. Когда эта функция применяется к значению v, это значение сопоставляется с каждым шаблоном pattern1 до patternn. Если одно из этих сопоставлений завершается успешно, то есть, если значение v соответствует шаблону patterni для некоторого i, то выражение expri, связанное с выбранным шаблоном, вычисляется, и его значение становится значением применения функции. Вычисление expri происходит в среде, обогащенной связываниями, выполненными во время сопоставления.

Если несколько шаблонов соответствуют аргументу v, выбирается тот, который встречается первым в определении функции. Если ни один из шаблонов не соответствует аргументу, генерируется исключение Match_failure.

# (function (0, 0) -> "both zero"
          | (0, _) -> "first only zero"
          | (_, 0) -> "second only zero"
          | (_, _) -> "neither zero")
  (7, 0);;

- : string = "second only zero"

Другая форма определения функции вводится ключевым словом fun:

fun parameter1 … parametern -> expr

Это выражение эквивалентно:

fun parameter1 -> … fun parametern -> expr
# let f = (fun a -> fun b -> fun c -> a + b + c)
  let g = (fun a b c -> a + b + c);;

val f : int -> int -> int -> int = 
val g : int -> int -> int -> int = 

Необязательное ограничение типа typexpr может быть добавлено перед -> для принудительного соответствия типа результата ограничению typexpr:

fun parameter1 … parametern : typexpr -> expr

эквивалентно

fun parameter1 -> … fun parametern -> (expr : typexpr )

Обратите внимание на небольшое синтаксическое различие между ограничением типа на последнем параметре

fun parameter1 … (parametern:typexpr)-> expr

и ограничением на результате

fun parameter1 … parametern: typexpr -> expr
# let eq = fun (a : int) (b : int) -> a = b
  let eq2 = fun a b : bool -> a = b
  let eq3 = fun (a : int) (b : int) : bool -> a = b;;

val eq : int -> int -> bool = 
val eq2 : 'a -> 'a -> bool = 
val eq3 : int -> int -> bool = 

Шаблоны параметров ~lab и ~(lab [: typ]) являются сокращениями соответственно для ~lab:lab и ~lab:(lab [: typ]), и аналогично для их необязательных аналогов.

# let bool_map ~cmp:(cmp : int -> int -> bool) l =
    List.map cmp l

  let bool_map' ~(cmp : int -> int -> bool) l =
    List.map cmp l;;

val bool_map : cmp:(int -> int -> bool) -> int list -> (int -> bool) list =
  
val bool_map' : cmp:(int -> int -> bool) -> int list -> (int -> bool) list =
  

Функция вида fun ? lab :( pattern = expr0 ) -> expr эквивалентна

fun ? lab : ident -> let pattern = match ident with Some ident -> ident | None -> expr0 in expr

где ident — новая переменная, за исключением того, что она не определена, когда вычисляется expr0.

# let open_file_for_input ?binary filename =
    match binary with
    | Some true -> open_in_bin filename
    | Some false | None -> open_in filename

  let open_file_for_input' ?(binary=false) filename =
    if binary then open_in_bin filename else open_in filename;;

val open_file_for_input : ?binary:bool -> string -> in_channel = 
val open_file_for_input' : ?binary:bool -> string -> in_channel = 

После этих двух преобразований выражения имеют вид

fun [label1] pattern1 -> … fun [labeln] patternn -> expr

Если мы игнорируем метки, которые будут иметь значение только при применении функции, это эквивалентно

function pattern1 -> … function patternn -> expr

То есть, выражение fun выше вычисляется в карированную функцию с n аргументами: после применения этой функции n раз к значениям v1 … vn, значения будут сопоставлены параллельно с шаблонами pattern1 … patternn. Если сопоставление успешно, функция возвращает значение expr в среде, обогащенной привязками, выполненными во время сопоставлений. Если сопоставление не удается, возникает исключение Match_failure.

Ограничительные условия в сопоставлениях с шаблонами

Случаи сопоставления с шаблонами (в конструкциях function, match и try) могут включать выражения-ограничители, которые представляют собой произвольные булевы выражения, которые должны вычисляться в true для выбора случая сопоставления. Ограничители находятся непосредственно перед маркером -> и вводятся с помощью ключевого слова when:

function pattern1 [when cond1] -> expr1
| …
| patternn [when condn] -> exprn

Сопоставление происходит, как описано выше, за исключением того, что если значение соответствует некоторому шаблону patterni, который имеет ограничитель condi, то выражение condi вычисляется (в среде, обогащенной привязками, выполненными во время сопоставления). Если condi вычисляется в true, то expri вычисляется и его значение возвращается как результат сопоставления, как обычно. Но если condi вычисляется в false, сопоставление возобновляется с шаблонов, следующих за patterni.

# let rec repeat f = function
    | 0 -> ()
    | n when n > 0 -> f (); repeat f (n - 1)
    | _ -> raise (Invalid_argument "repeat");;

val repeat : (unit -> 'a) -> int -> unit = 

Локальные определения

Конструкции let и let rec связывают имена значений локально. Конструкция

let pattern1 = expr1 and … and patternn = exprn in expr

вычисляет expr1 … exprn в каком-то неопределенном порядке и сопоставляет их значения с шаблонами pattern1 … patternn. Если сопоставления успешны, expr вычисляется в среде, обогащенной привязками, выполненными во время сопоставления, и значение expr возвращается как значение всего выражения let. Если одно из сопоставлений не удается, возникает исключение Match_failure.

# let v =
    let x = 1 in [x; x; x]

  let v' =
    let a, b = (1, 2) in a + b

  let v'' =
    let a = 1 and b = 2 in a + b;;

val v : int list = [1; 1; 1]
val v' : int = 3
val v'' : int = 3

Предоставляется альтернативный синтаксис для связывания переменных с функциональными значениями: вместо написания

let ident = fun parameter1 … parameterm -> expr

в выражении let, можно вместо этого написать

let ident parameter1 … parameterm = expr
# let f = fun x -> fun y -> fun z -> x + y + z

  let f' = fun x y z -> x + y + z

  let f'' x y z = x + y + z;;

val f : int -> int -> int -> int = 
val f' : int -> int -> int -> int = 
val f'' : int -> int -> int -> int = 

Рекурсивные определения имен вводятся с помощью let rec:

let rec pattern1 = expr1 and … and patternn = exprn in expr

Единственное отличие от конструкции let, описанной выше, состоит в том, что привязки имен к значениям, выполняемые сопоставлением с шаблонами, считаются уже выполненными, когда вычисляются выражения expr1 до exprn. То есть, выражения expr1 до exprn могут ссылаться на идентификаторы, которые связаны одним из шаблонов pattern1, …, patternn, и ожидать, что они будут иметь то же значение, что и в expr, теле конструкции let rec.

# let rec even =
    function 0 -> true | n -> odd (n - 1)
  and odd =
    function 0 -> false | n -> even (n - 1)
  in
    even 1000;;

- : bool = true

Гарантируется, что рекурсивное определение будет вести себя, как описано выше, если выражения expr1 до exprn являются определениями функций (fun … или function …), а шаблоны pattern1 … patternn являются просто именами значений, как в:

let rec name1 = fun … and … and namen = fun … in expr

Это определяет name1 … namen как взаимно рекурсивные функции, локальные для expr.

Поведение других форм определений let rec зависит от реализации. Текущая реализация также поддерживает определенный класс рекурсивных определений не функциональных значений, как объясняется в разделе ‍12.1.

Локальные исключения

(Введено в OCaml 4.04)

Можно определить локальные исключения в выражениях: let exception constr-decl in expr .

# let map_empty_on_negative f l =
    let exception Negative in
    let aux x = if x < 0 then raise Negative else f x in
      try List.map aux l with Negative -> [];;

val map_empty_on_negative : (int -> 'a) -> int list -> 'a list = 

Синтаксическая область видимости конструктора исключения — внутреннее выражение, но ничто не мешает значениям исключений, созданным с помощью этого конструктора, выходить за пределы этой области видимости. Два выполнения приведенного выше определения приводят к двум несовместимым конструкторам исключений (как и для любого определения исключения). Например:

# let gen () = let exception A in A

  let () = assert(gen () = gen ());;

Exception: Assert_failure ("expr.etex", 3, 9).

Явные полиморфные аннотации типов

(Введено в OCaml 3.12)

Полиморфные аннотации типов в определениях let ведут себя аналогично полиморфным методам:

let pattern1 : typ1 … typn . typexpr = expr

Эти аннотации явно требуют, чтобы определенное значение было полиморфным, и позволяют использовать этот полиморфизм в рекурсивных вхождениях (при использовании let rec). Однако следует отметить, что это обычный полиморфный тип, унифицируемый с любым его экземпляром.

7.3 Конструкции управления

Последовательность

Выражение expr1 ; expr2 сначала вычисляет expr1, затем expr2 и возвращает значение expr2.

# let print_pair (a, b) =
    print_string "(";
    print_string (string_of_int a);
    print_string ",";
    print_string (string_of_int b);
    print_endline ")";;

val print_pair : int * int -> unit = 

Условное выражение

Выражение if expr1 then expr2 else expr3 вычисляется в значение expr2, если expr1 вычисляется в булево значение true, и в значение expr3, если expr1 вычисляется в булево значение false.

# let rec factorial x =
    if x <= 1 then 1 else x * factorial (x - 1);;

val factorial : int -> int = 

Часть else expr3 может быть опущена, в этом случае она по умолчанию равна else ().

# let debug = ref false

  let log msg =
    if !debug then prerr_endline msg;;

val debug : bool ref = {contents = false}
val log : string -> unit = 

Выражение case

Выражение

match expr
with pattern1 -> expr1
| …
| patternn -> exprn

сопоставляет значение expr с образцами pattern1 до patternn. Если сопоставление с patterni завершается успешно, вычисляется связанное выражение expri, и его значение становится значением всего выражения match. Вычисление expri происходит в среде, дополненной привязками, выполненными во время сопоставления. Если несколько образцов соответствуют значению expr, выбирается тот, который встречается первым в выражении match.

# let rec sum l =
    match l with
    | [] -> 0
    | h :: t -> h + sum t;;

val sum : int list -> int = 

Если ни один из образцов не соответствует значению expr, возникает исключение Match_failure.

# let unoption o =
    match o with
    | Some x -> x ;;

Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
None

val unoption : 'a option -> 'a = 
# let l = List.map unoption [Some 1; Some 10; None; Some 2];;

Exception: Match_failure ("expr.etex", 2, 2).

Булевы операторы

Выражение expr1 && expr2 вычисляется в true, если и expr1, и expr2 вычисляются в true; в противном случае оно вычисляется в false. Сначала вычисляется первый компонент, expr1. Второй компонент, expr2, не вычисляется, если первый компонент вычисляется в false. Следовательно, выражение expr1 && expr2 ведет себя точно так же, как

if expr1 then expr2 else false.

Выражение expr1 || expr2 вычисляется в true, если одно из выражений expr1 и expr2 вычисляется в true; в противном случае оно вычисляется в false. Сначала вычисляется первый компонент, expr1. Второй компонент, expr2, не вычисляется, если первый компонент вычисляется в true. Следовательно, выражение expr1 || expr2 ведет себя точно так же, как

if expr1 then true else expr2.

Булевы операторы & и or являются устаревшими синонимами для (соответственно) && и ||.

# let xor a b =
    (a || b) && not (a && b);;

val xor : bool -> bool -> bool = 

Циклы

Выражение while expr1 do expr2 done многократно вычисляет expr2, пока expr1 вычисляется как true. Условие цикла expr1 вычисляется и проверяется в начале каждой итерации. Всё выражение while … done вычисляется в единичное значение ().

# let chars_of_string s =
    let i = ref 0 in
    let chars = ref [] in
      while !i < String.length s do
        chars := s.[!i] :: !chars;
        i := !i + 1
      done;
      List.rev !chars;;

val chars_of_string : string -> char list = 

В качестве частного случая, while true do expr done имеет полиморфный тип, позволяющий использовать его вместо любого выражения (например, как ветвь любого сопоставления с образцом).

Выражение for name = expr1 to expr2 do expr3 done сначала вычисляет выражения expr1 и expr2 (границы) в целочисленные значения n и p. Затем тело цикла expr3 многократно вычисляется в среде, где name последовательно связывается со значениями n, n+1, …, p−1, p. Тело цикла никогда не вычисляется, если n > p.

# let chars_of_string s =
    let l = ref [] in
      for p = 0 to String.length s - 1 do
        l := s.[p] :: !l
      done;
      List.rev !l;;

val chars_of_string : string -> char list = 

Выражение for name = expr1 downto expr2 do expr3 done вычисляется аналогично, за исключением того, что name последовательно связывается со значениями n, n−1, …, p+1, p. Тело цикла никогда не вычисляется, если n < p.

# let chars_of_string s =
    let l = ref [] in
      for p = String.length s - 1 downto 0 do
        l := s.[p] :: !l
      done;
      !l;;

val chars_of_string : string -> char list = 

В обоих случаях всё выражение for вычисляется в единичное значение ().

Обработка исключений

Выражение

try ‍ expr
with pattern1 -> expr1
| …
| patternn -> exprn

вычисляет выражение expr и возвращает его значение, если вычисление expr не вызывает никаких исключений. Если вычисление expr вызывает исключение, значение исключения сопоставляется с образцами pattern1 до patternn. Если сопоставление с patterni успешно, связанное выражение expri вычисляется, и его значение становится значением всего выражения try. Вычисление expri происходит в среде, дополненной связываниями, выполненными во время сопоставления. Если несколько образцов соответствуют значению expr, выбирается тот, который встречается первым в выражении try. Если ни один из образцов не соответствует значению expr, значение исключения снова вызывается, тем самым прозрачно «проходя» через конструкцию try.

# let find_opt p l =
    try Some (List.find p l) with Not_found -> None;;

val find_opt : ('a -> bool) -> 'a list -> 'a option = 

7.4 Операции над структурами данных

Продукты

Выражение expr1 , … , exprn вычисляется в n-кортеж значений выражений expr1 до exprn. Порядок вычисления подвыражений не определен.

# (1 + 2 * 3, (1 + 2) * 3, 1 + (2 * 3));;

- : int * int * int = (7, 9, 7)

Варианты

Выражение constr expr вычисляется в унарное значение варианта, конструктор которого — constr, а аргумент — значение expr. Аналогично, выражение constr ( expr1 , … , exprn ) вычисляется в n-арное значение варианта, конструктор которого — constr, а аргументы — значения expr1, …, exprn.

Выражение constr (expr1, …, exprn) вычисляется в значение варианта, конструктор которого — constr, а аргументы — значения expr1 … exprn.

# type t = Var of string | Not of t | And of t * t | Or of t * t

  let test = And (Var "x", Not (Or (Var "y", Var "z")));;

type t = Var of string | Not of t | And of t * t | Or of t * t
val test : t = And (Var "x", Not (Or (Var "y", Var "z")))

Для списков предоставляется некоторый синтаксический сахар. Выражение expr1 :: expr2 обозначает конструктор ( :: ), примененный к аргументам ( expr1 , expr2 ), и, следовательно, вычисляется в список, голова которого — значение expr1, а хвост — значение expr2. Выражение [ expr1 ; … ; exprn ] эквивалентно expr1 :: … :: exprn :: [], и, следовательно, вычисляется в список, элементами которого являются значения expr1 до exprn.

# 0 :: [1; 2; 3] = 0 :: 1 :: 2 :: 3 :: [];;

- : bool = true

Полиморфные варианты

Выражение `tag-name expr вычисляется в значение полиморфного варианта, тег которого — tag-name, а аргумент — значение expr.

# let with_counter x = `V (x, ref 0);;

val with_counter : 'a -> [> `V of 'a * int ref ] = 

Записи

Выражение { field1 [= expr1] ; … ; fieldn [= exprn ]} вычисляется в значение записи { field1 = v1; …; fieldn = vn }, где vi — значение expri для i = 1,… , n. Одиночный идентификатор fieldk обозначает fieldk = fieldk, а квалифицированный идентификатор module-path . fieldk обозначает module-path . fieldk = fieldk. Все поля field1 до fieldn должны принадлежать одному и тому же типу записи; каждое поле этого типа записи должно встречаться в выражении записи ровно один раз, хотя они могут появляться в любом порядке. Порядок, в котором вычисляются expr1 до exprn, не указан. После каждого поля можно добавлять необязательные ограничения типа { field1 : typexpr1 = expr1 ;… ; fieldn : typexprn = exprn }, чтобы принудительно установить тип fieldk совместимым с typexprk.

# type t = {house_no : int; street : string; town : string; postcode : string}

  let address x =
    Printf.sprintf "The occupier\n%i %s\n%s\n%s"
      x.house_no x.street x.town x.postcode;;

type t = {
  house_no : int;
  street : string;
  town : string;
  postcode : string;
}
val address : t -> string = 

Выражение { expr with field1 [= expr1] ; … ; fieldn [= exprn] } создает новую запись с полями field1 … fieldn, равными expr1 … exprn, а все остальные поля имеют то же значение, что и в записи expr. Другими словами, оно возвращает неглубокую копию записи expr, за исключением полей field1 … fieldn, которые инициализируются значениями expr1 … exprn. Как и ранее, одиночный идентификатор fieldk обозначает fieldk = fieldk, квалифицированный идентификатор module-path . fieldk обозначает module-path . fieldk = fieldk, и можно добавить необязательное ограничение типа для каждого обновляемого поля с помощью { expr with field1 : typexpr1 = expr1 ; … ; fieldn : typexprn = exprn }.

# type t = {house_no : int; street : string; town : string; postcode : string}

  let uppercase_town address =
    {address with town = String.uppercase_ascii address.town};;

type t = {
  house_no : int;
  street : string;
  town : string;
  postcode : string;
}
val uppercase_town : t -> t = 

Выражение expr1 . field вычисляет expr1 как значение записи и возвращает значение, связанное с field в этом значении записи.

Выражение expr1 . field <- expr2 вычисляет expr1 как значение записи, которое затем изменяется на месте путем замены значения, связанного с field в этой записи, на значение expr2. Эта операция разрешена только в том случае, если field был объявлен как mutable в определении типа записи. Всё выражение expr1 . field <- expr2 вычисляется в единичное значение ().

# type t = {mutable upper : int; mutable lower : int; mutable other : int}

  let stats = {upper = 0; lower = 0; other = 0}

  let collect =
    String.iter
      (function
       | 'A'..'Z' -> stats.upper <- stats.upper + 1
       | 'a'..'z' -> stats.lower <- stats.lower + 1
       | _ -> stats.other <- stats.other + 1);;

type t = { mutable upper : int; mutable lower : int; mutable other : int; }
val stats : t = {upper = 0; lower = 0; other = 0}
val collect : string -> unit = 

Массивы

Выражение [| expr1 ; … ; exprn |] вычисляется в массив из n элементов, элементы которого инициализируются значениями expr1 до exprn соответственно. Порядок, в котором вычисляются эти выражения, не определен.

Выражение expr1 .( expr2 ) возвращает значение элемента под номером expr2 в массиве, обозначаемом expr1. Первый элемент имеет номер 0; последний элемент имеет номер n−1, где n — размер массива. Если доступ выходит за границы, возникает исключение Invalid_argument.

Выражение expr1 .( expr2 ) <- expr3 изменяет на месте массив, обозначаемый expr1, заменяя элемент под номером expr2 на значение expr3. Если доступ выходит за границы, возникает исключение Invalid_argument. Значение всего выражения — ().

# let scale arr n =
    for x = 0 to Array.length arr - 1 do
      arr.(x) <- arr.(x) * n
    done

  let x = [|1; 10; 100|]
  let _ = scale x 2;;

val scale : int array -> int -> unit = 
val x : int array = [|2; 20; 200|]

Строки

Выражение expr1 .[ expr2 ] возвращает значение символа под номером expr2 в строке, обозначаемой expr1. Первый символ имеет номер 0; последний символ имеет номер n−1, где n — длина строки. Если доступ выходит за границы, возникает исключение Invalid_argument.

# let iter f s =
    for x = 0 to String.length s - 1 do f s.[x] done;;

val iter : (char -> 'a) -> string -> unit = 

Выражение expr1 .[ expr2 ] <- expr3 изменяет на месте строку, обозначаемую expr1, заменяя символ под номером expr2 на значение expr3. Если доступ выходит за границы, возникает исключение Invalid_argument. Значение всего выражения — (). Примечание: эта возможность предоставляется только для обратной совместимости со старыми версиями OCaml и будет удалена в будущей версии. В новом коде следует использовать последовательности байтов и функцию Bytes.set.

7.5 Операторы

Символы из класса infix-symbol, а также ключевые слова *, +, -, -., =, !=, <, >, or, ||, &, &&, :=, mod, land, lor, lxor, lsl, lsr и asr могут появляться в инфиксной позиции (между двумя выражениями). Символы из класса prefix-symbol, а также ключевые слова - и -. могут появляться в префиксной позиции (перед выражением).

# (( * ), ( := ), ( || ));;

- : (int -> int -> int) * ('a ref -> 'a -> unit) * (bool -> bool -> bool) =
(, , )

Инфиксные и префиксные символы не имеют фиксированного значения: они просто интерпретируются как применения функций, связанных с именами, соответствующими символам. Выражение prefix-symbol expr интерпретируется как применение ( prefix-symbol ) expr. Аналогично, выражение expr1 infix-symbol expr2 интерпретируется как применение ( infix-symbol ) expr1 expr2.

В таблице ниже перечислены символы, определенные в начальной среде, и их начальное значение. (Дополнительные сведения см. в описании модуля основной библиотеки Stdlib в главе ‍28). Их значение может быть изменено в любое время с помощью let ( infix-op ) name1 name2 = …

# let ( + ), ( - ), ( * ), ( / ) = Int64.(add, sub, mul, div);;

val ( + ) : int64 -> int64 -> int64 = 
val ( - ) : int64 -> int64 -> int64 = 
val ( * ) : int64 -> int64 -> int64 = 
val ( / ) : int64 -> int64 -> int64 = 

Примечание: операторы &&, || и ~- обрабатываются специально, и не рекомендуется изменять их значение.

Ключевые слова - и -. могут выступать как инфиксные и префиксные операторы. Когда они выступают как префиксные операторы, они интерпретируются соответственно как функции (~-) и (~-.).

Оператор Начальное значение
+ Сложение целых чисел.
- (инфиксная) Вычитание целых чисел.
~- - (префиксная) Изменение знака целого числа.
* Умножение целых чисел.
/ Целочисленное деление. Вызывает Division_by_zero, если второй аргумент равен нулю.
mod Взятие остатка от деления целых чисел. Вызывает Division_by_zero, если второй аргумент равен нулю.
land Побитовая логическая операция «и» для целых чисел.
lor Побитовая логическая операция «или» для целых чисел.
lxor Побитовая логическая операция «исключающее или» для целых чисел.
lsl Побитовый логический сдвиг влево для целых чисел.
lsr Побитовый логический сдвиг вправо для целых чисел.
asr Побитовый арифметический сдвиг вправо для целых чисел.
+. Сложение чисел с плавающей точкой.
-. (инфиксная) Вычитание чисел с плавающей точкой.
~-. -. (префиксная) Изменение знака числа с плавающей точкой.
*. Умножение чисел с плавающей точкой.
/. Деление чисел с плавающей точкой.
** Возведение в степень чисел с плавающей точкой.
@ Конкатенация списков.
^ Конкатенация строк.
! Разъясняющая ссылка (возвращает текущее содержимое ссылки).
:= Присваивание ссылки (обновление ссылки, указанной в качестве первого аргумента, значением второго аргумента).
= Проверка структурного равенства.
<> Проверка структурного неравенства.
== Проверка физического равенства.
!= Проверка физического неравенства.
< Проверка «меньше».
<= Проверка «меньше или равно».
> Проверка «больше».
>= Проверка «больше или равно».
&& & Логическое И.
|| or Логическое ИЛИ.

7.6 Объекты

Создание объектов

Когда class-path вычисляется как тело класса, new class-path вычисляется как новый объект, содержащий переменные и методы экземпляра этого класса.

# class of_list (lst : int list) = object
    val mutable l = lst
    method next =
      match l with
      | [] -> raise (Failure "empty list");
      | h::t -> l <- t; h
  end

  let a = new of_list [1; 1; 2; 3; 5; 8; 13]

  let b = new of_list;;

class of_list :
  int list -> object val mutable l : int list method next : int end
val a : of_list = 
val b : int list -> of_list = 

Когда class-path вычисляется как функция класса, new class-path вычисляется как функция, ожидающая то же количество аргументов и возвращающая новый объект этого класса.

Непосредственное создание объектов

Создание объекта напрямую с помощью конструкции object class-body end оперативно эквивалентно локальному определению class class-name = object class-body end —см. разделы 11.9.2 и следующие за ним для синтаксиса class-body— и непосредственному созданию одного объекта из него с помощью new class-name.

# let o =
    object
      val secret = 99
      val password = "unlock"
      method get guess = if guess <> password then None else Some secret
    end;;

val o : < get : string -> int option > = 

Типизация непосредственных объектов немного отличается от явного определения класса в двух аспектах. Во-первых, выведенный тип объекта может содержать свободные переменные типа. Во-вторых, поскольку тело класса непосредственного объекта никогда не будет расширено, его собственный тип может быть объединен с закрытым типом объекта.

Вызов метода

Выражение expr # method-name вызывает метод method-name объекта, обозначаемого expr.

# class of_list (lst : int list) = object
    val mutable l = lst
    method next =
      match l with
      | [] -> raise (Failure "empty list");
      | h::t -> l <- t; h
  end

  let a = new of_list [1; 1; 2; 3; 5; 8; 13]

  let third = ignore a#next; ignore a#next; a#next;;

class of_list :
  int list -> object val mutable l : int list method next : int end
val a : of_list = 
val third : int = 2

Если method-name является полиморфным методом, его тип должен быть известен в точке вызова. Это верно, например, если expr является именем нового объекта (let ident = new class-path … ) или если существует ограничение типа. Принцип вывода может быть проверен в режиме -principal.

Доступ к переменным экземпляра и их изменение

Переменные экземпляра класса видны только в теле методов, определенных в этом же классе или классе, который наследуется от класса, определяющего переменные экземпляра. Выражение inst-var-name вычисляет значение данной переменной экземпляра. Выражение inst-var-name <- expr присваивает значение expr переменной экземпляра inst-var-name, которая должна быть изменяемой. Всё выражение inst-var-name <- expr вычисляет в ().

# class of_list (lst : int list) = object
    val mutable l = lst
    method next =
      match l with                            (* access instance variable *)
      | [] -> raise (Failure "empty list");
      | h::t -> l <- t; h                     (* modify instance variable *)
  end;;

class of_list :
  int list -> object val mutable l : int list method next : int end

Дублирование объектов

Объект можно дублировать с помощью библиотечной функции Oo.copy (см. модуль Oo). Внутри метода выражение {< [inst-var-name [= expr] { ; inst-var-name [= expr] }] >} возвращает копию self с заданными переменными экземпляра, замененными значениями соответствующих выражений. Одиночное имя переменной экземпляра id означает id = id. Другие переменные экземпляра имеют в возвращаемом объекте то же значение, что и в self.

# let o =
    object
      val secret = 99
      val password = "unlock"
      method get guess = if guess <> password then None else Some secret
      method with_new_secret s = {< secret = s >}
    end;;

val o : < get : string -> int option; with_new_secret : int -> 'a > as 'a =
  

7.7 Приведение типов

Выражения, тип которых содержит объектные или полиморфные вариантные типы, могут быть явно приведены (ослаблены) к супертипу. Выражение (expr :> typexpr) приводит выражение expr к типу typexpr. Выражение (expr : typexpr1 :> typexpr2) приводит выражение expr из типа typexpr1 к типу typexpr2.

Первый оператор иногда не сможет привести выражение expr из типа typ1 к типу typ2, даже если тип typ1 является подтипом типа typ2: в текущей реализации он раскрывает только два уровня сокращений типов, содержащих объекты и/или полиморфные варианты, сохраняя только рекурсию, когда она явная в типе класса (для объектов). В качестве исключения из вышеуказанного алгоритма, если и выведенный тип expr, и typ являются основными (т.е. не содержат переменных типа), первый оператор ведет себя как второй, принимая выведенный тип expr как typ1. В случае сбоя с первым оператором, следует использовать второй.

Привести выражение expr из типа typ1 к типу typ2 возможно только в том случае, если тип expr является экземпляром typ1 (как для аннотации типа), и typ1 является подтипом typ2. Тип приведенного выражения является экземпляром typ2. Если типы содержат переменные, они могут быть инстанцированы алгоритмом подтипирования, но это делается только после определения того, является ли typ1 потенциальным подтипом typ2. Это означает, что типизация может завершиться неудачей на этом последнем этапе унификации, даже если некоторый экземпляр typ1 является подтипом некоторого экземпляра typ2. В следующих параграфах мы опишем используемое отношение подтипирования.

Объектные типы

Фиксированный объектный тип допускает в качестве подтипа любой объектный тип, который включает все его методы. Типы методов должны быть подтипами тех, которые находятся в супертипе. А именно,

< met1 : typ1 ; … ; metn : typn >

является супертипом

< met1 : typ′1 ; … ; metn : typ′n ; metn+1 : typ′n+1 ; … ; metn+m : typ′n+m ‍[; ..] >

который может содержать многоточие .., если каждый typi является супертипом соответствующего typ′i.

Мономорфный тип метода может быть супертипом полиморфного типа метода. А именно, если typ является экземпляром typ′, то 'a1 … 'an . typ′ является подтипом typ.

Внутри определения класса вновь определенные типы недоступны для подтипирования, так как сокращения типов еще не полностью определены. Есть исключение для приведения self к (точному) типу своего класса: это разрешено, если тип self не появляется в контравариантной позиции в типе класса, т.е. если нет бинарных методов.

Полиморфные вариантные типы

Полиморфный вариантный тип typ является подтипом другого полиморфного вариантного типа typ′ если верхняя граница typ (т.е. максимальное множество конструкторов, которые могут появиться в экземпляре typ) включено в нижнюю границу typ′, и типы аргументов для конструкторов typ являются подтипами тех, которые находятся в typ′. А именно,

[[<] `C1 of typ1 | … | `Cn of typn ]

который может быть сокращаемым типом, является подтипом

[[>] `C1 of typ′1 | … | `Cn of typ′n | `Cn+1 of typ′n+1 | … | `Cn+m of typ′n+m ]

который может быть расширяемым типом, если каждый typi является подтипом typ′i.

Дисперсия

Другие типы не вводят нового подтипирования, но они могут распространять подтипирование своих аргументов. Например, typ1 * typ2 является подтипом typ′1 * typ′2, когда typ1 и typ2 являются соответственно подтипами typ′1 и typ′2. Для функциональных типов отношение более тонкое: typ1 -> typ2 является подтипом typ′1 ‍-> typ′2, если typ1 является супертипом typ′1, а typ2 является подтипом typ′2. По этой причине функциональные типы ковариантны по своему второму аргументу (как кортежи), но контравариантны по своему первому аргументу. Изменяемые типы, такие как array или ref, не являются ни ковариантными, ни контравариантными, они невариантны, то есть они не распространяют подтипирование.

Для определяемых пользователем типов дисперсия выводится автоматически: параметр является ковариантным, если он имеет только ковариантные вхождения, контравариантным, если он имеет только контравариантные вхождения, без дисперсии, если он не имеет вхождений, и невариантным в противном случае. Параметр без дисперсии может свободно изменяться при подтипировании, он не должен быть подтипом или супертипом. Для абстрактных и закрытых типов дисперсию необходимо задавать явно (см. раздел ‍11.8.1), иначе по умолчанию используется невариантность. Это также относится к ограниченным аргументам в определениях типов.

7.8 Другие

Проверка утверждений

OCaml поддерживает конструкцию assert для проверки утверждений отладки. Выражение assert expr вычисляет выражение expr и возвращает (), если expr вычисляется как true. Если оно вычисляется как false, возникает исключение Assert_failure с именем исходного файла и местоположением expr в качестве аргументов. Проверку утверждений можно отключить с помощью параметра компилятора -noassert. В этом случае expr вообще не вычисляется.

# let f a b c =
    assert (a <= b && b <= c);
    (b -. a) /. (c -. b);;

val f : float -> float -> float -> float = 

В качестве особого случая, assert false сводится к raise (Assert_failure ...), что дает ему полиморфный тип. Это означает, что он может использоваться вместо любого выражения (например, как ветвь любого сопоставления с образцом). Это также означает, что «утверждения» assert false не могут быть отключены с помощью параметра -noassert.

# let min_known_nonempty = function
    | [] -> assert false
    | l -> List.hd (List.sort compare l);;

val min_known_nonempty : 'a list -> 'a = 

Ленивые выражения

Выражение lazy expr возвращает значение v типа Lazy.t, которое инкапсулирует вычисление expr. Аргумент expr не вычисляется в этой точке программы. Вместо этого его вычисление будет выполнено в первый раз, когда функция Lazy.force применяется к значению v, возвращая фактическое значение expr. Последующие применения Lazy.force к v не вычисляют expr снова. Применения Lazy.force могут быть неявными через сопоставление с образцом (см. ‍11.6).

# let lazy_greeter = lazy (print_string "Hello, World!\n");;

val lazy_greeter : unit lazy_t = 
# Lazy.force lazy_greeter;;

Hello, World!
- : unit = ()

Локальные модули

Выражение let module module-name = module-expr in expr локально связывает выражение модуля module-expr с идентификатором module-name во время вычисления выражения expr. Затем он возвращает значение expr. Например:

# let remove_duplicates comparison_fun string_list =
    let module StringSet =
      Set.Make(struct type t = string
                      let compare = comparison_fun end)
    in
      StringSet.elements
        (List.fold_right StringSet.add string_list StringSet.empty);;

val remove_duplicates :
  (string -> string -> int) -> string list -> string list = 

Локальное открытие

Выражения let open module-path in expr и module-path.(expr) строго эквивалентны. Эти конструкции локально открывают модуль, на который ссылается путь к модулю module-path, в соответствующей области действия выражения expr.

# let map_3d_matrix f m =
    let open Array in
      map (map (map f)) m

  let map_3d_matrix' f =
    Array.(map (map (map f)));;

val map_3d_matrix :
  ('a -> 'b) -> 'a array array array -> 'b array array array = 
val map_3d_matrix' :
  ('a -> 'b) -> 'a array array array -> 'b array array array = 

Когда тело локального открытого выражения ограничено [ ], [| |] или { }, скобки могут быть опущены. Для выражения скобки также могут быть опущены для {< >}. Например, module-path.[expr] эквивалентно module-path.([expr]), и module-path.[| expr |] эквивалентно module-path.([| expr |]).

# let vector = Random.[|int 255; int 255; int 255; int 255|];;

val vector : int array = [|220; 90; 247; 144|]
« ШаблоныОпределения типов и исключений »
Copyright © 2024 Institut National de Recherche en Informatique et en Automatique

© 1995-2024 INRIA.
https://ocaml.org/manual/5.2/expr.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API