Spec-Zone.ru › OCaml 4.14

9.7 Выражения

  • 9.7.1 Приоритет и ассоциативность
  • 9.7.2 Базовые выражения
  • 9.7.3 Управляющие структуры
  • 9.7.4 Операции над структурами данных
  • 9.7.5 Операторы
  • 9.7.6 Объекты
  • 9.7.7 Преобразования типов
  • 9.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 исключение constr-decl in expr
∣ let модуль 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, атрибуты, узлы расширения и расширенные операторы индексирования.

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

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

Конструктор или оператор Ассоциативность
prefix-символ –
. .( .[ .{ (см. раздел 10.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)

9.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) (см. подраздел 9.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: если функция имеет известную арность, все аргументы неописанные, и их количество соответствует числу необязательных параметров, то метки игнорируются, а необязательные параметры сопоставляются в порядке их определения. Необязательные аргументы по умолчанию. Это игнорирование меток не рекомендуется и приводит к предупреждению, см. 11.5.1.

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

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

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

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

Если несколько шаблонов соответствуют аргументу 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 параметр1 … параметрn -> выражение

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

fun параметр1 -> … fun параметрn -> выражение
# 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 = 

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

fun параметр1 … параметрn : тип -> выражение

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

fun параметр1 -> … fun параметрn -> (выражение : тип )

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

fun параметр1 … (параметрn:тип)-> выражение

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

fun параметр1 … параметрn: тип -> выражение
# 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 = 

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

# 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 ? метка :( шаблон = выражение0 ) -> выражение эквивалентна

fun ? метка : идентификатор -> let шаблон = match идентификатор with Some идентификатор -> идентификатор | None -> выражение0 in выражение

где идентификатор — новая переменная, за исключением того, что не указано, когда вычисляется выражение0.

# 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 [метка1] шаблон1 -> … fun [меткаn] шаблонn -> выражение

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

function шаблон1 -> … function шаблонn -> выражение

То есть, выражение fun выше вычисляет функцию с n аргументами: после применения этой функции n раз к значениям v1 … vn, значения будут сопоставляться параллельно с шаблонами шаблон1 … шаблонn. Если сопоставление успешно, функция возвращает значение выражения в окружении, обогащённом привязками, выполненными во время сопоставлений. Если сопоставление терпит неудачу, возбуждается исключение 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 зависит от реализации. Текущая реализация также поддерживает определенный класс рекурсивных определений значений, не являющихся функциями, как описано в разделе 10.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). Однако следует отметить, что это обычный полиморфный тип, унифицируемый с любым экземпляром самого себя.

9.7.3 Структуры управления

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

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

# 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 выражение1 then выражение2 else выражение3 вычисляет значение выражения2, если выражение1 вычисляет логическое значение true, и вычисляет значение выражения3, если выражение1 вычисляет логическое значение false.

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

val factorial : int -> int = 

Часть else выражение3 можно опустить, в этом случае она по умолчанию равна 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 выражение
with шаблон1 -> выражение1
| …
| шаблонn -> выражениеn

сопоставляет значение выражения с шаблонами шаблон1 по шаблонn. Если сопоставление с шаблономi успешно, вычисляется связанное выражение выражениеi, и его значение становится значением всего выражения match. Вычисление выраженияi происходит в среде, дополненной привязками, выполненными во время сопоставления. Если несколько шаблонов соответствуют значению выражения, выбирается тот, который встречается первым в выражении match.

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

val sum : int list -> int = 

Если ни один из шаблонов не соответствует значению выражения, генерируется исключение Match_failure.

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

  let l = List.map unoption [Some 1; Some 10; None; Some 2];;

Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
None
Exception: Match_failure ("expr.etex", 2, 2).

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

Выражение выражение1 && выражение2 вычисляет true, если оба выражение1 и выражение2 вычисляются как true; в противном случае оно вычисляет false. Первый компонент выражение1 вычисляется первым. Второй компонент выражение2 не вычисляется, если первый компонент вычисляется как false. Таким образом, выражение выражение1 && выражение2 ведет себя точно так же, как

if выражение1 then выражение2 else false.

Выражение выражение1 || выражение2 вычисляет true, если одно из выражений выражение1 и выражение2 вычисляется как true; в противном случае оно вычисляет false. Первый компонент выражение1 вычисляется первым. Второй компонент выражение2 не вычисляется, если первый компонент вычисляется как true. Таким образом, выражение выражение1 || выражение2 ведет себя точно так же, как

if выражение1 then true else выражение2.

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

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

val xor : bool -> bool -> bool = 

Циклы

Выражение while выражение1 do выражение2 done повторяет вычисление выражения2, пока выражение1 вычисляет true. Условие цикла выражение1 вычисляется и проверяется в начале каждой итерации. Весь цикл 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 = 

Выражение for имя = выражение1 to выражение2 do выражение3 done сначала вычисляет выражения выражение1 и выражение2 (границы) в целые значения n и p. Затем тело цикла выражение3 многократно вычисляется в среде, где имя последовательно связывается со значениями 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 имя = выражение1 downto выражение2 do выражение3 done вычисляется аналогично, за исключением того, что имя последовательно связывается со значениями 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 выражение
with шаблон1 -> выражение1
| …
| шаблонn -> выражениеn

вычисляет выражение выражение и возвращает его значение, если вычисление выражения не вызывает исключение. Если вычисление выражения вызывает исключение, значение исключения сопоставляется с шаблонами шаблон1 до шаблонn. Если сопоставление с шаблономi успешно, то связанное выражение выражениеi вычисляется, и его значение становится значением всего выражения try. Вычисление выраженияi выполняется в среде, обогащённой связываниями, выполненными во время сопоставления. Если несколько шаблонов соответствуют значению выражения, выбирается тот, который встречается первым в выражении try. Если ни один из шаблонов не соответствует значению выражения, значение исключения поднимается снова, тем самым прозрачно «проходя» через конструкцию 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 = 

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

Произведения

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

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

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

Варианты

Выражение конструктор выражение вычисляется до унарного значения варианта, конструктором которого является конструктор, а аргументом – значение выражения. Аналогично, выражение конструктор ( выражение1 , … , выражениеn ) вычисляется до n-арного значения варианта, конструктором которого является конструктор, а аргументами – значения выражение1, …, выражениеn.

Выражение конструктор (выражение1, …, выражениеn) вычисляется к значению варианта, конструктором которого является конструктор, а аргументами – значения выражение1 … выражениеn.

# 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")))

Для списков предоставляется некоторый синтаксический сахар. Выражение выражение1 :: выражение2 обозначает конструктор ( :: ), применённый к аргументам ( выражение1 , выражение2 ), и поэтому вычисляется до списка, у которого голова – значение выражение1, а хвост – значение выражение2. Выражение [ выражение1 ; … ; выражениеn ] эквивалентно выражение1 :: … :: выражениеn :: [], и поэтому вычисляется до списка, элементами которого являются значения выражение1 до выражениеn.

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

- : bool = true

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

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

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

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

Записи

Выражение { поле1 [= выражение1] ; … ; полеn [= выражениеn ]} вычисляет значение записи { поле1 = v1; …; полеn = vn }, где vi — значение выраженияi для i = 1,… , n. Одиночный идентификатор полеk соответствует полеk = полеk, а квалифицированный идентификатор путь-модуля . полеk — путь-модуля . полеk = полеk. Поля поле1 до полеn должны принадлежать одному типу записи; каждое поле этого типа записи должно появиться ровно один раз в выражении записи, хотя они могут появиться в любом порядке. Порядок вычисления выражение1 до выражениеn не определён. После каждого поля можно добавить необязательные ограничения типа { поле1 : типвыраж1 = выражение1 ;… ; полеn : типвыражn = выражениеn } для принудительного соответствия типа поляk типу типвыражk.

# 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 = 

Выражение { выражение с полем1 [= выражение1] ; … ; полеn [= выражениеn] } создаёт новую запись с полями поле1 … полеn, равными выражение1 … выражениеn, а все остальные поля имеют то же значение, что и в записи выражение. Другими словами, возвращает поверхностную копию записи выражение, за исключением полей поле1 … полеn, которые инициализируются выражение1 … выражениеn. Как и прежде, одиночный идентификатор полеk соответствует полеk = полеk, а квалифицированный идентификатор путь-модуля . полеk соответствует путь-модуля . полеk = полеk, и можно добавить необязательное ограничение типа на каждое обновляемое поле с { выражение с полем1 : типвыраж1 = выражение1 ; … ; полеn : типвыражn = выражениеn }.

# 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 = 

Выражение выражение1 . поле вычисляет выражение1 до значения записи и возвращает значение, соответствующее полю в этом значении записи.

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

# 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 = 

Массивы

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

END_OF_DOCUMENT_MARKER

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

9.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 в главе 25). Их значение может быть изменено в любое время с помощью 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 Логическое «или» для булевых значений.

Объекты

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

Когда 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 — см. разделы 9.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 > = 

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

Вызов метода

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

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

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

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

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

Типы объектов

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

< метод1 : тип1 ; … ; методn : типn >

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

< метод1 : тип′1 ; … ; методn : тип′n ; методn+1 : тип′n+1 ; … ; методn+m : тип′n+m [; ..] >

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

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

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

Типы полиморфных вариантов

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

[[<] `Констр1 of тип1 | … | `Констрn of типn ]

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

[[>] `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, не являются ни ковариантными, ни контравариантными, они невариантны, то есть не распространяют подтипизацию.

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

Прочие

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

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 могут быть неявными через сопоставление с образцом (см. 9.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 имя-модуля = выражение-модуля in выражение локально связывает выражение модуля выражение-модуля с идентификатором имя-модуля во время вычисления выражения выражение. Затем оно возвращает значение выражение. Например:

# 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 путь-к-модулю in выражение и путь-к-модулю.(выражение) строго эквивалентны. Эти конструкции локально открывают модуль, на который ссылается путь к модулю путь-к-модулю, в соответствующей области выражения выражение.

# 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 = 
END_OF_DOCUMENT_MARKER

Когда тело локального выражения открыто и ограничено символами [ ], [| |] или { }, скобки можно опустить. Для выражений, скобки также можно опустить для {< >}. Например, 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 = [|116; 127; 85; 129|]

© 1995-2022 INRIA.
https://v2.ocaml.org/releases/4.14/htmlman/expr.html

Spec-Zone.ru

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