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 Локальные элементы структуры
  • 7.9 Прочее
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 local-definition in expr
∣ ( expr :> typexpr )
∣ ( expr : typexpr :> typexpr )
∣ assert expr
∣ lazy expr
∣ local-open-shorthand
∣ 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-shorthand ::=
∣ 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) –
#… левая
вызов функции, вызов конструктора, применение тега, assert, lazy левая
- -. (префиксные) –
**… lsl lsr asr правая
*… /… %… mod land lor lxor левая
+… -… левая
:: правая
@… ^… правая
=… <… >… |… &… $… != левая
& && правая
or || правая
, –
<- := правая
if –
; правая
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 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 = 

Выражение сопоставления

Выражение

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 в главе 29.) Их значение можно изменить в любой момент с помощью 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 — синтаксис class-body описан в разделе 11.9.2 и следующих за ним — и немедленному созданию из него единственного объекта с помощью 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 5.5; let open, let exception и let module поддерживались и ранее.)

Форма let local-definition in expr вводит элемент структуры (см. 11.11.2) local-definition в область видимости на время вычисления expr.

Эта конструкция эквивалентна локальному введению структуры

struct local-definition let body = expr end

в контексте выражения let с возвратом значения body в качестве результата.

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

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

Выражения 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|]

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

Выражение let module-definition in expr локально связывает определение модуля module-definition на время вычисления выражения 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 = 

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

(Введены в 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).

Таким же образом можно вводить расширяемые варианты типов (см. 12.14). В примере ниже показана функция, использующая локальный эффект для преобразования итератора в последовательность («инверсия управления»).

# let invert (type a) ~(iter : (a -> unit) -> unit) : a Seq.t =
    let type _ Effect.t += Yield : a -> unit Effect.t in
    let yield v = Effect.perform (Yield v) in
    fun () -> match iter yield with
    | () -> Seq.Nil
    | effect Yield v, k -> Seq.Cons (v, Effect.Deep.continue k)
  ;;

val invert : iter:(('a -> unit) -> unit) -> 'a Seq.t = 

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

Форма let type-definition in expr вводит тип (или набор взаимно рекурсивных типов) в область видимости выражения:

# let f x y =
    let type t = {x: float; y: float} in
    let norm p = p.x *. p.x +. p.y *. p.y in
    norm {x; y}
  ;;

val f : float -> float -> float = 

Компилятор проверяет, что значения, созданные с помощью этого типа, не выходят за пределы этой области видимости:

# let f x y =
    let type t = {x: float; y: float} in
    let twice p = {x = p.x +. p.x; y = p.y +. p.y} in
    twice {x; y}
  ;;

Error: This expression has type t but an expression was expected of type 'a
       The type constructor t would escape its scope

7.9 Прочее

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

В 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 = ()
« ШаблоныОпределения типов и исключений »
Copyright © 2026 Национальный институт исследований в области информатики и автоматики

© 1995-2026 INRIA.
https://ocaml.org/manual/5.5/expr.html

Spec-Zone.ru

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