Scala позволяет параметрам функции быть вызов по имени с использованием синтаксиса «=>
». Например, логическая функция and
может быть определена как:
def and(x: Boolean, y: => Boolean): Boolean =
if (x) y else false
Второй параметр — вызов по имени. Это гарантирует ожидаемое поведение «короткого замыкания» — если x
равно false
, y
вообще не будет оцениваться.
В общем, это полезно для функций, в которых параметр может вообще не использоваться.
Как это можно сделать в OCaml?
Я знаю, что в OCaml есть конструкции для ленивых вычислений, но мне не ясно, можно ли их использовать для передачи параметров вызова по имени и если да, то как.
Во-первых, по своей конструкции булевы операторы ленивы:
# false || (print_endline "second"; true);;
second
- : bool = true
# true || (print_endline "second"; true);;
- : bool = true
# true && (print_endline "second"; false);;
second
- : bool = false
# false && (print_endline "second"; false);;
- : bool = false
Затем, для большего количества пользовательских функций, вы действительно должны использовать lazy (с огромным преимуществом, что ленивые значения запоминаются после оценки):
let fib n =
let rec aux n b a =
Printf.printf "%d %d %d\n" n b a;
if n <= 0 then a else aux (n - 1) (a + b) b
in
aux n 1 0
let f a b = if b > 0 then Lazy.force a + 1 else b
(* val f : int lazy_t -> int -> int = <fun> *)
let () =
let r = lazy (fib 10) in
let r2 = lazy (fib 1000000) in
Printf.printf "%d\n" (f r 1 + f r 2 + f r2 0)
Результат будет:
10 1 0
9 1 1
8 2 1
7 3 2
6 5 3
5 8 5
4 13 8
3 21 13
2 34 21
1 55 34
0 89 55
112
r
оценивался только один раз, а r2
никогда не оценивался.
Логические операторы определены как примитивы компилятора (см. github.com/ocaml/ocaml/blob/trunk/lambda/translprim.ml#L139), но я никогда не копался так глубоко в компиляторе, чтобы узнать, как они написаны и что они делают. Если кто-то вроде @gasche хотел бы кратко объяснить это здесь, это было бы неплохо
Я не уверен, что тут объяснять? И ||
, и &&
определены как примитивы компилятора в стандартной библиотеке с external (||): bool -> bool -> bool = "%seqor"
и external (&&): bool -> bool -> bool = "%seqand"
(имена примитивов компилятора начинаются с %
). И компилятор реализует обычную семантику короткого замыкания для %seqor
и %seqand
.
Вы на самом деле указали на то, что нужно объяснить, And the compiler is implementing the usual short-circuiting semantics for %seqor and %seqand
. Поскольку оп спросил Are they just intrinsic, and unable to be defined in the language itself?
, я ответил, что они являются внутренними, но я не думаю, что их можно определить в самом языке, но я не смог бы это объяснить. Вот и все, «можете ли вы определить такой оператор, как &&
, не используя lazy?». Я чувствую, что вы не можете, но я не могу объяснить, что для них делает компилятор.
Я действительно не понимаю вашего комментария @octachron. Я написал, что логические операторы определены как примитивы компилятора, но я не знаю, как они компилируются, и, следовательно, не могу объяснить, что за этим стоит, а вы прокомментировали: «логические операторы являются примитивами компилятора, и компилятор их компилирует, я не знаю». не знаю, что тут объяснять». Мой вопрос был конкретно о том, «как они компилируются и выполнимо ли это в программе OCaml?»
Возможно, лучше было бы сформулировать, что никакой OCaml не всегда вызывается по значению, и ||
не может быть реализован как функция больше, чем if ... then ... else..
. Другими словами, x || y
и x && y
являются сокращением семантики для if x then true else y
и if x then y else false
с обычной семантикой для if ... then ... else
. В частности, ((||) true) (Printf.printf "not short-circuiting\n"; true)
печатает на стандартный вывод, потому что (||) true
покупает замыкание, которое больше не является примитивом компилятора.
Но sequand и sequor определены в файлах OCaml, поэтому я не совсем понимаю, почему вы не могли их переписать самостоятельно. Если бы они были примитивами в файлах C, я бы это понял. Но это не так.
Семантика этих примитивов определяется компилятором. Чтобы переписать ||
в OCaml, вам нужно будет закодировать примитив %seqand
в другой примитив, например if ... then ... else ...
. Обратите внимание, что компилятор OCaml полностью написан на OCaml, только среда выполнения написана на C. Тем не менее, семантика языка реализации компилятора не имеет отношения к семантике скомпилированных программ. Например, вы можете полностью написать компилятор Haskell на OCaml.
Я полагаю, что более простой способ, которым я мог бы задать свой дополнительный вопрос, заключается в том, можно ли определить функцию с параметром вызова по имени таким образом, чтобы обеспечить тот же интерфейс для вызывающей стороны, что и функция вызова по значению . Так обстоит дело в Scala и в логических операторах OCaml. Но для пользовательских функций в OCaml, как я понял из этого разговора, ответ отрицательный.
Я понял из вашего ответа, что если мы попытаемся определить оператор
and
таким образом, он будет иметь типbool -> bool lazy_t -> bool
, что потребует вызовов для переноса второго аргумента вlazy
. Если это так, то как на самом деле определяются логические операторы в OCaml? Являются ли они просто внутренними и не могут быть определены в самом языке?