Подтвердить что ты не робот

Почему ocaml нужны как "let", так и "let rec"?

Возможный дубликат:
Почему функции в Ocaml/F # не рекурсивные по умолчанию?

OCaml использует let для определения новой функции или let rec для определения рекурсивной функции. Зачем это нужно обоим? Не могли бы мы просто использовать let для всего?

Например, чтобы определить нерекурсивную функцию-преемник и рекурсивный факториал в OCaml (фактически, в интерпретаторе OCaml), я мог бы написать

let succ n = n + 1;;

let rec fact n =
    if n = 0 then 1 else n * fact (n-1);;

В то время как в Haskell (GHCI) я могу написать

let succ n = n + 1

let fact n =
    if n == 0 then 1 else n * fact (n-1)

Почему OCaml различает let и let rec? Это проблема производительности или что-то более тонкое?

4b9b3361

Ответ 1

Ну, имея как доступный, а не только один, дает программисту более жесткий контроль над областью. С let x = e1 in e2 привязка присутствует только в среде e2, а при let rec x = e1 in e2 привязка присутствует в средах e1 и e2.

(Edit: Я хочу подчеркнуть, что это не проблема производительности, это не имеет никакого значения.)

Вот две ситуации, когда использование этой нерекурсивной привязки полезно:

  • Затенение существующего определения с уточнением, использующим старую привязку. Что-то вроде: let f x = (let x = sanitize x in ...), где sanitize - это функция, которая гарантирует, что вход имеет какое-то желательное свойство (например, он принимает норму, возможно, ненормированного вектора и т.д.). Это очень полезно в некоторых случаях.

  • метапрограммирование, например, макросъемка. Представьте, что я хочу определить макрос SQUARE(foo), который desugars в let x = foo in x * x, для любого выражения foo. Мне нужна эта привязка, чтобы избежать дублирования кода в выходе (я не хочу, чтобы SQUARE(factorial n) вычислял factorial n дважды). Это только гигиенично, если привязка let не рекурсивна, иначе я не мог бы написать let x = 2 in SQUARE(x) и получить правильный результат.

Поэтому я утверждаю, что очень важно иметь доступ как рекурсивную, так и нерекурсивную привязку. Теперь поведение по умолчанию let-binding является детерминированным. Можно сказать, что let x = ... является рекурсивным, и нужно использовать let nonrec x = ... для получения нерекурсивного связующего. Выбор одного по умолчанию или другого - это вопрос, какой стиль программирования вы хотите одобрить, и есть веские причины сделать любой выбор. Haskell страдает¹ из недоступности этого нерекурсивного режима, а OCaml имеет точно такой же дефект на уровне уровня: type foo = ... является рекурсивным, и нет нерекурсивного варианта - см. это сообщение в блоге.

¹: когда Google Code Search был доступен, я использовал его для поиска в коде Haskell для шаблона let x' = sanitize x in .... Это обычное обходное решение, когда нерекурсивное связывание недоступно, но оно менее безопасно, потому что вы рискуете написать x вместо x' по ошибке позже - в некоторых случаях вы хотите иметь оба доступных, поэтому выбираете другую имя может быть добровольным. Хорошей идиомой было бы использовать более длинное имя переменной для первого x, например unsanitized_x. Во всяком случае, просто поиск x' litterally (нет другого имени переменной) и x1 показал много результатов. Эрланг (и весь язык, который пытается сделать переменную тень трудной: Coffeescript и т.д.) Имеет еще худшие проблемы такого рода.

Тем не менее, выбор привязки Haskell, рекурсивный по умолчанию (а не нерекурсивный), безусловно, имеет смысл, поскольку он согласуется с ленивой оценкой по умолчанию, что делает его очень простым для создания рекурсивных значений - по умолчанию языки имеют больше ограничений, на которых рекурсивные определения имеют смысл.