Модуль Fun
module Fun: sig .. end
Манипулирование функциями.
См. примеры ниже.
- Начиная с версии 4.08
Комбинаторы
val id : 'a -> 'a
id — это тождественная функция. Для любого аргумента x, id x равняется x.
val const : 'a -> 'b -> 'a
const c — это функция, которая всегда возвращает значение c. Для любого аргумента x, (const c) x равняется c.
val compose : ('b -> 'c) -> ('a -> 'b) -> 'a -> 'c
compose f g — это композиция функций: сначала применяется g, затем f. Для любых аргументов f, g и x, compose f g x равняется f (g x).
- Начиная с версии 5.2
val flip : ('a -> 'b -> 'c) -> 'b -> 'a -> 'c
flip f меняет порядок аргументов бинарной функции f на обратный. Для любых аргументов x и y, (flip f) x y равняется f y x.
val negate : ('a -> bool) -> 'a -> bool
negate p — это отрицание функции-предиката p. Для любого аргумента x, (negate p) x равняется not (p x).
Обработка исключений
val protect : finally:(unit -> unit) -> (unit -> 'a) -> 'a
protect ~finally work вызывает work (), а затем finally (), прежде чем work () вернёт своё значение или завершится с исключением. Во втором случае исключение будет повторно вызвано после finally (). Если finally () вызывает исключение, вместо него вызывается исключение Fun.Finally_raised.
protect можно использовать для обеспечения локальных инвариантов независимо от того, завершается ли work () успешно или вызывает исключение. Однако это не защищает от неожиданных исключений, возникающих внутри finally (), таких как Out_of_memory, Stack_overflow или асинхронные исключения, вызванные обработчиками сигналов (например, Sys.Break).
Примечание: если finally вызывает исключения других типов, это считается ошибкой программирования, поскольку любое исключение, вызванное в work (), будет потеряно в случае исключения Fun.Finally_raised. Поэтому следует обрабатывать такие исключения внутри блока finally.
exception Finally_raised of exn
Finally_raised exn вызывается функцией protect ~finally work, когда finally вызывает исключение exn. Это исключение обозначает либо неожиданное исключение, либо ошибку программирования. Как правило, исключение Finally_raised не следует перехватывать иначе как в обработчике, перехватывающем все исключения.
Примеры
Комбинаторы
Комбинаторы позволяют создавать анонимные функции простым, а иногда и более читабельным способом. Лучше всего использовать их как краткосрочные аргументы, а не как отдельные определения. В примерах ниже это показано главным образом на модуле List.
id
List.init с самим индексом
# List.init 3 Fun.id;; - : int list = [0; 1; 2]
Использование List.filter_map для int option list с целью отфильтровать элементы None
# List.filter_map Fun.id [None; Some 2; Some 3; None; Some 5];; - : int list = [2; 3; 5]
Условная передача функций типа foo -> foo или передача их в качестве аргументов — ещё одна область, где может быть полезен id. Рассмотрим простую функцию журналирования, которая выводит строку, но позволяет пользователю предварительно отформатировать её перед выводом, например, добавить метку времени.
let log ?(preformat : string -> string = Fun.id) message =
print_endline (preformat message)
Если можно накапливать замыкания, id часто используется в качестве базового случая, ничего не делающего. Рассмотрим функцию, которая объединяет список унарных функций в цепочку:
let rec chain = function
| [] -> Fun.id
| f :: fs -> fun x -> f (chain fs x)
const
List.init список нулей
# List.init 3 (Fun.const 0);; - : int list = [0; 0; 0]
Предикат, допускающий все значения: его можно передать любой функции фильтрации, например List.filter, чтобы отключить фильтрацию и получить все значения
# List.filter (Fun.const true) [1; 2; 3];; - : int list = [1; 2; 3]
Обратите внимание, что применение const (...) вычисляет выражение (...) один раз и возвращает функцию, в которой сохранён только результат этого вычисления. Чтобы показать это, рассмотрим случай, когда (...) — вызов Random.bool():
List.init n (Fun.const (Random.bool())) для любого n > 0 будет иметь ровно два возможных результата:
[true; true; ...; true][false; false; ...; false]
тогда как List.init n (fun _ -> Random.bool()) будет иметь 2n возможных результатов, поскольку случайное значение выбирается для каждого элемента.
В качестве примера из реальной практики можно рассмотреть String.spellcheck с постоянным максимальным расстоянием, равным 2, вместо максимального расстояния, изменяющегося по умолчанию.
let spellcheck known_words word =
let dict_iter yield = List.iter yield known_words in
String.spellcheck ~max_dist:(Fun.const 2) dict_iter word
flip
Использование flip для обращения компаратора, переданного в List.sort, что позволяет сортировать в обратном порядке
# List.sort (Fun.flip Int.compare) [5; 3; 9; 0; 1; 6; 8];; - : int list = [9; 8; 6; 5; 3; 1; 0]
Обращение списка путём накопления нового списка с помощью List.fold_left, которая ожидает, что аккумулятор будет первым аргументом переданной ей функции. Мы передаём List.cons, у которой список является вторым аргументом, поэтому здесь полезен flip
# List.fold_left (Fun.flip List.cons) [] [1; 2; 3];; - : int list = [3; 2; 1]
Интересно, что flip работает и с функциями, которые не являются бинарными: он меняет местами первые два аргумента, оставляя остальные в прежнем порядке. Это объясняется тем, что функция, принимающая n+2 аргументов, концептуально является бинарной функцией, которая возвращает функцию, принимающую n аргументов. Для функции f : a -> b -> c -> d:
-
flip fбудет иметь типb -> a -> c -> d -
fun x -> flip (f x)будет иметь типa -> c -> b -> d
Использовать flip таким образом с небинарными функциями не рекомендуется из-за негативного влияния на читабельность и понятность кода.
negate
Используется преимущественно для инвертирования предиката в функциях, которым он передаётся, например List.find_all и подобных
Поиск всех списков, которые не пусты, с помощью List.is_empty
# List.find_all (Fun.negate List.is_empty) [[0]; [1; 2; 3]; []; [4; 5]];; - : int list list = [[0]; [1; 2; 3]; [4; 5]]
Поиск всех незанятых путей в заданном списке путей с помощью Sys.file_exists
# List.find_all (Fun.negate Sys.file_exists);; - : string list -> string list = <fun>
compose
Применение List.map к парам с функцией, применяемой ко второму элементу
# List.map (Fun.compose String.length snd) [1, "one"; 2, "two"; 3, "three"];; - : int list = [3; 3; 5]
Возможная реализация Fun.negate
let negate f = Fun.compose not f
В примере chain можно было использовать compose в рекурсивной ветви
let rec chain = function
| [] -> Fun.id
| f :: fs -> Fun.compose f (chain fs)
Или даже лаконичнее:
let chain fs = List.fold_right Fun.compose fs Fun.id
В примере spellcheck можно было использовать compose и flip, чтобы сократить определение функции до следующего вида:
# Fun.compose
(String.spellcheck ~max_dist:(Fun.const 2))
(Fun.flip List.iter)
;;
- : string list -> string -> string list = <fun>
Как видно здесь, это существенно ухудшает читабельность и затрудняет понимание функции. Ни String.spellcheck, ни Fun.flip не являются унарными, поэтому в этом определении частичное применение взаимодействует с ними нетривиальным образом.
В OCaml в целом не рекомендуется широко использовать эти комбинаторы не только потому, что они могут быстро ухудшить читабельность и затруднить понимание кода, но и потому, что полученные функции часто имеют форму значений и, следовательно, подпадают под ограничение значений (см. раздел 6.1.2 руководства).
© 1995-2026 INRIA.
https://ocaml.org/manual/5.5/api/Fun.html