Мои старые заметки по ML говорят, что
let (𝑣₁, … , 𝑣ₙ) = (𝑡₁, … , 𝑡ₙ) in 𝑡′
является синтаксическим сахаром для
(λ 𝑣ₙ. … (λ 𝑣₁. 𝑡′)𝑡₁ … )𝑡ₙ
и что
let (𝑣₁, 𝑣₂) = 𝑡 𝑡′ in 𝑡″
эквивалентно
let 𝑣 = 𝑡 𝑡′ in
let 𝑣₂ = snd 𝑣 in
let 𝑣₁ = fst 𝑣 in
𝑡″
где
𝑣
(с индексом или без) обозначает переменную,𝑡
(с нижним или верхним индексом или без него) обозначает термин, иfst
и snd
доставляют первый и второй компоненты пары соответственно.Мне интересно, правильно ли я понял порядок оценки, потому что я не заметил исходную ссылку. Может ли кто-нибудь ((подтвердить или отклонить) и (дать ссылку))?
Неважно, что это:
let 𝑣 = 𝑡 𝑡′ in
let 𝑣₂ = snd 𝑣 in
let 𝑣₁ = fst 𝑣 in
𝑡″
Или:
let 𝑣 = 𝑡 𝑡′ in
let 𝑣₁ = fst 𝑣 in
let 𝑣₂ = snd 𝑣 in
𝑡″
Так как ни fst
, ни snd
не имеют побочных эффектов. Побочные эффекты могут существовать при оценке 𝑡 𝑡′
, но это делается до того, как произойдет привязка let.
Кроме того, как в:
let (𝑣₁, 𝑣₂) = 𝑡 𝑡′ in 𝑡″
Ни 𝑣₁
, ни 𝑣₂
не зависят от значения, связанного с другим, чтобы определить его значение, поэтому порядок, в котором они связаны, снова, по-видимому, не имеет значения.
На все это может быть авторитетный ответ от тех, кто более глубоко знаком со стандартом SML или внутренней работой реализации OCaml. Я просто не уверен, какую практическую пользу это знание принесет.
В качестве практического теста запустим некоторый код, в котором мы связываем кортеж из нескольких выражений с побочными эффектами, чтобы соблюдать порядок оценки. В OCaml (5.0.0) наблюдается порядок вычислений справа налево. Мы наблюдаем то же самое, когда дело доходит до оценки содержимого списка, где эти выражения также имеют побочные эффекты.
# let f () = print_endline "f"; 1 in
let g () = print_endline "g"; 2 in
let h () = print_endline "h"; 3 in
let (a, b, c) = (f (), g (), h ()) in a + b + c;;
h
g
f
- : int = 6
# let f () = print_endline "f"; 1 in
let g () = print_endline "g"; 2 in
let h () = print_endline "h"; 3 in
let (c, b, a) = (h (), g(), f ()) in a + b + c;;
f
g
h
- : int = 6
# let f _ = print_endline "f"; 1 in
let g () = print_endline "g"; 2 in
let h () = print_endline "h"; 3 in
let a () = print_endline "a" in
let b () = print_endline "b" in
let (c, d, e) = (f [a (); b ()], g (), h ()) in
c + d + e;;
h
g
b
a
f
- : int = 6
В SML (SML/NJ v110.99.3) наблюдается обратное: вычисление выражений слева направо.
- let
= fun f() = (print "f\n"; 1)
= fun g() = (print "g\n"; 2)
= fun h() = (print "h\n"; 3)
= val (a, b, c) = (f(), g(), h())
= in
= a + b + c
= end;
f
g
h
val it = 6 : int
- let
= fun f() = (print "f\n"; 1)
= fun g() = (print "g\n"; 2)
= fun h() = (print "h\n"; 3)
= val (c, b, a) = (h(), g(), f())
= in
= a + b + c
= end;
h
g
f
val it = 6 : int
- let
= fun f _ = (print "f\n"; 1)
= fun g() = (print "g\n"; 2)
= fun h() = (print "h\n"; 3)
= fun a() = print "a\n"
= fun b() = print "b\n"
= val (c, d, e) = (f [a(), b()], g(), h())
= in
= c + d + e
= end;
a
b
f
g
h
val it = 6 : int
Ух ты! Спасибо! Такой же порядок оценки кортежа справа налево в OCaML 4.11.1 и порядок оценки кортежа слева направо в SMLNJ 110.79. Есть идеи о том, предназначены ли эти два порядка оценки (справа налево для OCaML и слева направо для SMLNJ) для двух компиляторов, и должны ли эти порядок оценки быть универсальными для двух компиляторов?
Я также протестировал MosML 2.10 (от ppa.launchpad.net/kflarsen/mosml/ubuntu/pool/main/m/mosml/…) на ваших примерах и получил порядок оценки кортежа слева направо, такой же что касается SMLNJ.
@AlbertNash для OCaml, порядок оценки намеренно оставлен неуказанным, чтобы вы даже не пытались на него полагаться. На практике это было справа налево в течение достаточно долгого времени, но некоторые реализации были слева направо.
@jthulhu Первые два примера на процитированной вами веб-странице по-прежнему дают тот же результат с OCaML 4.11.1: я получаю «World!Hello» и «argument». Итак, предложение «Сегодня все реализации Objective CAML оценивают аргументы слева направо». это просто неправда сейчас.
@jthulhu Ваш «порядок оценки намеренно оставлен неуказанным», однако, поддерживается «В Objective CAML порядок оценки аргументов не указан». с указанной вами веб-страницы. Немного натянуто распространить неспецификацию порядка вычисления аргументов функции на построение кортежа.
@AlbertNash действительно; как уже говорилось, некоторые реализации были слева направо, но это не так, как это реализовано в настоящее время.
Имейте в виду, что в OCaml из-за (ослабления the) ограничения значения let a = b in c
не эквивалентно (fun a -> c)b
. Контрпример
# let id = fun x -> x in id 5, id 'a';;
- : int * char = (5, 'a')
# (fun id -> id 5, id 'a')(fun x -> x)
Error: This expression has type char but an expression was expected of type int
#
Это означает, что они семантически не являются одной и той же конструкцией (let ... = ... in ...
строго более общая, чем другая).
Это происходит потому, что, как правило, система типов OCaml не допускает типы формы (∀α.α→α) → int * char
(поскольку их разрешение сделало бы ввод текста неразрешимым, что не очень практично), который был бы типом fun id -> id 5, id 'a'
. Вместо этого он прибегает к менее общему типу ∀α.(α→α) → α * α
, который не делает проверку типов, потому что вы не можете унифицировать как α
с char
, так и с int
.
@AlbertNash Я не слишком уверен в том, где вы видите опечатку, но, действительно, хотя люди обычно думают, что let a = b in c
определяется как (λa. c) b
, в OCaml это не так. Однако обратите внимание, что хотя эти две конструкции и не являются строго равными, они все же очень похожи: просто иногда OCaml не может проверить тип формы лямбда-оценки, но может проверить тип другой. Кроме того, на практике ни один язык программирования фактически не реализует привязки как оценку функции по соображениям производительности.
Разница между let 𝑎 = 𝑏 in 𝑐
и (fun 𝑎 -> 𝑐) 𝑏
заключается в проверке типов, а не в β-редукции (которая не зависит или, по крайней мере, должна быть независима от типов).
@AlbertNash ах да, я увидел опечатку, я исправил ее.
Связанный: stackoverflow.com/questions/43883010/…