Что ж, если оба они доступны вместо одного, это дает программисту более жесткий контроль над областью действия. В let x = e1 in e2
привязка присутствует только в среде e2
, в то время как в let rec x = e1 in e2
привязка присутствует как в средах e1
, так и e2
.
(Изменить: я хочу подчеркнуть, что это не проблема производительности, которая не имеет никакого значения вообще.)
Вот две ситуации, когда это не рекурсивное связывание полезно:
дублирование существующего определения с уточнением, использующим старое связывание. Что-то вроде: let f x = (let x = sanitize x in ...)
, где sanitize
- это функция, которая гарантирует, что вход имеет некоторое желаемое свойство (например, оно принимает норму возможно ненормализованного вектора и т. Д.). Это очень полезно в некоторых случаях.
метапрограммирование, например, написание макросов. Представьте, что я хочу определить макрос SQUARE(foo)
, который вводится в let x = foo in x * x
, для любого выражения foo
. Мне нужна эта привязка, чтобы избежать дублирования кода в выходных данных (я не хочу, чтобы SQUARE(factorial n)
вычислял factorial n
дважды). Это гигиенично, только если привязка let
не является рекурсивной, в противном случае я не смог бы написать let x = 2 in SQUARE(x)
и получить правильный результат.
Итак, я утверждаю, что действительно очень важно иметь как рекурсивное, так и нерекурсивное связывание. Теперь поведение let-привязки по умолчанию является предметом соглашения. Можно сказать, что let x = ...
является рекурсивным, и для получения нерекурсивного связующего нужно использовать let nonrec x = ...
. Выбор одного или другого по умолчанию зависит от того, какой стиль программирования вы предпочитаете, и есть веские причины для того, чтобы сделать любой выбор. Haskell страдает от недоступности этого нерекурсивного режима, и OCaml имеет точно такой же дефект на уровне типа: type foo = ...
является рекурсивным, и нет доступных нерекурсивных опций - см. это сообщение в блоге .
¹: когда Google Code Search был доступен, я использовал его для поиска в коде на Haskell шаблона let x' = sanitize x in ...
. Это обычный обходной путь, когда нерекурсивное связывание недоступно, но это менее безопасно, потому что вы рискуете позже написать x
вместо x'
по ошибке - в некоторых случаях вы хотите, чтобы оба были доступны, поэтому выбираете другое Имя может быть добровольным. Хорошая идиома - использовать более длинное имя переменной для первого x
, например, unsanitized_x
. В любом случае, просто поиск x'
буквально (без имени другой переменной) и x1
дали много результатов. У Эрланга (и всех языков, которые пытаются затруднить теневое копирование: Coffeescript и т. Д.) Проблемы еще хуже.
Тем не менее, выбор наличия рекурсивных привязок Haskell по умолчанию (а не нерекурсивных), безусловно, имеет смысл, поскольку это согласуется с отложенной оценкой по умолчанию, что позволяет действительно легко создавать рекурсивные значения - в то время как строгие языки по умолчанию имеют больше ограничений на рекурсивные определения.