11.7 Выражения
- 11.7.1 Приоритет и ассоциативность
- 11.7.2 Базовые выражения
- 11.7.3 Конструкции управления
- 11.7.4 Операции над структурами данных
- 11.7.5 Операторы
- 11.7.6 Объекты
- 11.7.7 Преобразования типов
- 11.7.8 Прочее
|
|
|
См. также следующие расширения языка: модули первого класса, переопределение в операциях open, синтаксис доступа к Bigarray, атрибуты, узлы расширения и расширенные операторы индексации.
11.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)
11.7.2 Базовые выражения
Константы
Выражение, состоящее из константы, вычисляет эту константу. Например, 3.14 или [||].
Пути значений
Выражение, состоящее из пути доступа, вычисляет значение, связанное с этим путём в текущей среде вычисления. Путь может быть именем значения или путём доступа к компоненту значения модуля.
# Float.ArrayLabels.to_list;; - : Float.ArrayLabels.t -> float list =
Выражения в скобках
Выражения ( выражение ) и begin выражение end имеют то же значение, что и выражение. Эти два конструкта семантически эквивалентны, но использование 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 = Выражения в скобках могут содержать ограничение типа, как в ( выражение : выражение_типа ). Это ограничение заставляет тип выражение соответствовать выражение_типа.
Выражения в скобках также могут содержать преобразования ( выражение [: выражение_типа] :> выражение_типа) (см. подпункт 11.7.7 ниже).
Применение функции
Применение функции обозначается сопоставлением (возможно, помеченных) выражений. Выражение выражение аргумент1 … аргументn вычисляет выражение выражение и те, которые появляются в аргумент1 до аргументn. Выражение выражение должно вычислиться до функционального значения f, которое затем применяется к значениям аргумент1, …, аргументn.
Порядок, в котором вычисляются выражения выражение, аргумент1, …, аргументn, не определён.
# 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]
Если параметр указан как необязательный (метка, предваряемая ?) в типе выражение, соответствующий аргумент будет автоматически обернут конструктором 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 поддерживает полные применения с опущенными метками: если функция имеет известную арность, все аргументы неопределённые, а их количество совпадает с количеством необязательных параметров, тогда метки игнорируются, а необязательные параметры сопоставляются в порядке их определения. Необязательные аргументы по умолчанию. Это опущение меток не рекомендуется и приводит к предупреждению, см. 13.5.1.
Определение функции
Для определения функций предоставляются две синтаксические формы. Первая форма вводится ключевым словом function:
|
Это выражение вычисляет функциональное значение с одним аргументом. Когда эта функция применяется к значению 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:
Это выражение эквивалентно:
# 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 =
Дополнительно можно добавить ограничение типа тип перед -> для обеспечения совместимости типа результата с ограничением тип:
эквивалентно
Обратите внимание на небольшое синтаксическое различие между ограничением типа для последнего параметра
и ограничением типа для результата
# 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 ) -> выражение эквивалентна
где идентификатор — новая переменная, за исключением того, что не указано, когда вычисляется выражение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 выше вычисляет функцию с n аргументами: после применения этой функции n раз к значениям v1 … vn, значения будут сопоставляться параллельно с шаблонами шаблон1 … шаблонn. Если сопоставление успешно, функция возвращает значение выражения в окружении, обогащённом привязками, выполненными во время сопоставлений. Если сопоставление терпит неудачу, возбуждается исключение Match_failure.
Условные выражения в сопоставлениях с шаблонами
Случаи сопоставления с шаблоном (в конструкциях function, match и try) могут включать условные выражения, которые являются произвольными булевыми выражениями, которые должны вычисляться до значения true для выбора случая соответствия. Условные выражения появляются непосредственно перед маркером -> и вводятся ключевым словом when.
|
Сопоставление происходит, как описано ранее, за исключением того, что если значение соответствует некоторому шаблону 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 связывают имена значений локально. Конструкция
оценивает 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, можно записать
# 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 , описанной выше, состоит в том, что привязки имён к значениям, выполненные с помощью сопоставления с образцом, считаются уже выполненными при оценке выражений 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 — просто имена значений, как в:
Это определяет 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 rec). Однако обратите внимание, что это обычный полиморфный тип, унифицируемый с любым экземпляром самого себя.
11.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
Выражение
|
сопоставляет значение выражения с шаблонами шаблона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 ведет себя точно так же, как
Выражение выражение1 || выражение2 вычисляется в true, если одно из выражений выражение1 и выражение2 вычисляется в true; в противном случае вычисляется в false. Первый компонент выражение1 вычисляется сначала. Второй компонент выражение2 не вычисляется, если первый компонент вычисляется в true. Таким образом, выражение выражение1 || выражение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 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 вычисляется до единичного значения ().
Обработка исключений
Выражение
|
вычисляет выражение 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 = Операции над структурами данных
Произведения
Выражение 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 ] =
Записи
Выражение { поле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.
11.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 в главе 27 для получения дополнительной информации). Их значение может быть изменено в любое время с помощью 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 | Булева дизъюнкция. |
11.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 > = Типизация непосредственных объектов немного отличается от явного определения класса в двух отношениях. Во-первых, выведенный тип объекта может содержать свободные переменные типа. Во-вторых, поскольку тело класса непосредственного объекта никогда не будет расширено, его тип `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`.
END_OF_DOCUMENT_MARKER# 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 =
11.7.7 Приведение типов
Выражения, тип которых содержит типы объектов или полиморфных вариантов, могут быть явно приведены (ослаблены) к надтипу. Выражение (выражение :> тип) приводит выражение выражение к типу тип. Выражение (выражение : тип1 :> тип2) приводит выражение выражение от типа тип1 к типу тип2.
Первый оператор иногда не сможет привести выражение выражение от типа тип1 к типу тип2, даже если тип тип1 является подтипом типа тип2: в текущей реализации он расширяет только два уровня аббревиатур типов, содержащих объекты и/или полиморфные варианты, сохраняя только рекурсию, когда она явным образом указана в типе класса (для объектов). В качестве исключения из вышеописанного алгоритма, если тип, выведенный для выражения, и тип являются основными (то есть не содержат переменных типов), первый оператор ведет себя как второй, принимая выведенный тип выражения как тип1. В случае неудачи с первым оператором следует использовать второй.
Возможна только приведение выражения выражение от типа тип1 к типу тип2, если тип выражения является экземпляром тип1 (как для аннотации типа), и тип1 является подтипом тип2. Тип приведенного выражения является экземпляром тип2. Если типы содержат переменные, они могут быть инстанцированы алгоритмом подтипирования, но это делается только после определения, является ли тип1 потенциальным подтипом тип2. Это означает, что типизация может завершиться неудачей на этом последнем шаге унификации, даже если некоторый экземпляр тип1 является подтипом некоторого экземпляра тип2. В следующих абзацах мы описываем отношение подтипирования, используемое.
Типы объектов
Фиксированный тип объекта допускает в качестве подтипа любой тип объекта, который включает все его методы. Типы методов должны быть подтипами соответствующих типов в надтипе. А именно,
является надтипом
который может содержать многоточие .., если каждый типi является надтипом соответствующего тип′i.
Мономорфный тип метода может быть надтипом полиморфного типа метода. А именно, если тип является экземпляром тип′, то 'a1 … 'an . тип′ является подтипом тип.
Внутри определения класса новые типы недоступны для подтипирования, так как аббревиатуры типов еще не полностью определены. Существует исключение для приведения self к (точному) типу его класса: это разрешено, если тип self не появляется в контравариантной позиции в типе класса, то есть если нет бинарных методов.
Типы полиморфных вариантов
Тип полиморфного варианта тип является подтипом другого типа полиморфного варианта тип′ если верхняя граница тип (то есть максимальный набор конструкторов, который может появиться в экземпляре тип) включена в нижнюю границу тип′, и типы аргументов для конструкторов тип являются подтипами тех, что в тип′. А именно,
который может быть сжимаемым типом,
который может быть расширяемым типом, если каждый 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), в противном случае по умолчанию используется инвариантность. Это также относится к ограниченным аргументам в определениях типов.
11.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 имя-модуля = выражение-модуля 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 = Когда тело локального выражения открыто и ограничено символами [ ], [| |] или { }, скобки можно опустить. Для выражений, скобки также можно опустить для {< >}. Например, 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|]
© 1995-2022 INRIA.
https://v2.ocaml.org/releases/5.0/htmlman/expr.html