В этих конспектах лекций есть пример 6. Имена, области действия и привязки: это объясняет концепции, хотя мне не нравится их псевдокод:
thres:integer
function older(p:person):boolean
return p.age>thres
procedure show(p:person, c:function)
thres:integer
thres:=20
if c(p)
write(p)
procedure main(p)
thres:=35
show(p, older)
Насколько я могу судить, это будет следующее на схеме (с некоторыми, я надеюсь, более описательными именами:
(define cutoff 0) ; a
(define (above-cutoff? person)
(> (age person) cutoff))
(define (display-if person predicate)
(let ((cutoff 20)) ; b
(if (predicate person)
(display person))))
(define (main person)
(let ((cutoff 35)) ; c
(display-if person above-cutoff?)))
- При лексической области видимости cutoff в выше-cutoff? всегда относится к привязке a.
- С динамической областью видимости, реализованной в Common Lisp (и, я думаю, в большинстве современных языков с динамической областью видимости), значение cutoff в выше-cutoff? при использовании в качестве предикат в display-if, будет ссылаться на привязку b, так как в этом случае она самая последняя в стеке. Это неглубокая привязка.
- Таким образом, оставшийся вариант — глубокая привязка, и в результате значение cutoff находится в пределах выше-cutoff? см. привязку с.
Теперь давайте посмотрим на ваш пример:
(define x 0)
(define y 0)
(define (f z) (display (+ z y))
(define (g f) (let ((y 10)) (f x)))
(define (h) (let ((x 100)) (g f)))
(h)
Я собираюсь добавить несколько новых строк, чтобы упростить комментирование, и использовать комментарий, чтобы отметить каждую привязку каждой из переменных, которая связывается более одного раза.
(define x 0) ; x0
(define y 0) ; y0
(define (f z) ; f0
(display (+ z y)))
(define (g f) ; f1
(let ((y 10)) ; y1
(f x)))
(define (h)
(let ((x 100)) ; x1
(g f)))
Обратите внимание на f0 и f1. Это важно, потому что при глубокой привязке текущая среда функции, переданной в качестве аргумента, привязывается к этой среде. Это важно, потому что f передается как параметр g внутри f. Итак, рассмотрим все случаи:
- При лексической области видимости результат равен 0. Думаю, это самый простой случай.
- При динамической области видимости и неглубокой привязке ответ равен 110. (Значение z равно 100, а значение y равно 10.) Это ответ, который вы уже знаете, как получить.
- Наконец, динамическая область и глубокая привязка, вы получаете 100. В течение h вы передаете f в качестве параметра, и текущая область фиксируется, чтобы дать нам функцию (лямбда (z) (отображение (+ z 0))), которое для удобства мы назовем ff. Когда вы находитесь в g, вызов локальной переменной f фактически является вызовом ff, который вызывается с текущим значением. из x (от x1, 100), поэтому вы печатаете (+ 100 0), что равно 100.
Комментарии
Как я уже сказал, я думаю, что глубокая привязка довольно необычна, и я не знаю, действительно ли многие языки ее реализуют. Вы можете думать об этом как о том, что вы берете функцию, проверяете, есть ли у нее свободные переменные, а затем заполняете их значениями из текущей динамической среды. Я не думаю, что это на самом деле часто используется на практике, и, вероятно, поэтому вы получили комментарии с вопросами об этих терминах. Хотя я вижу, что в некоторых случаях это может быть полезно. Например, в Common Lisp, который имеет как лексические, так и динамические (называемые «специальными») переменные, многие параметры конфигурации системы являются динамическими. Это означает, что вы можете делать подобные вещи для печати в базе 16 (поскольку *print-radix* — это динамическая переменная):
(let ((*print-radix* 16))
(print value))
Но если вы хотите вернуть функцию, которая будет печатать что-то в базе 16, вы не можете сделать:
(let ((*print-radix* 16))
(lambda (value)
(print value)))
потому что кто-то может взять эту функцию, назовем ее print16 и сделать:
(let ((*print-radix* 10))
(print16 value))
и значение будет напечатано с основанием 10. Глубокая привязка позволит избежать этой проблемы. Тем не менее, вы также можете избежать этого с помощью мелкой привязки; ты просто возвращаешься
(lambda (value)
(let ((*print-radix* 16))
(print value)))
вместо.
Все сказанное, я думаю, что это обсуждение становится довольно странным, когда речь идет о «передаче функций в качестве аргументов». Это странно, потому что в большинстве языков выражение вычисляется для создания значения. переменная – это один из типов выражений, а результат вычисления переменной – это выражение этой переменной. Я подчеркиваю здесь «the», потому что так оно и есть: переменная имеет единственное значение в любой момент времени. Такое представление глубокой и поверхностной привязки приводит к тому, что переменной присваивается различное значение в зависимости от того, где она оценивается. Это кажется довольно странным. Что, на мой взгляд, имело бы гораздо больше смысла, так это обсуждение того, что вы получаете, когда вычисляете лямбда-выражение. Тогда вы могли бы спросить, «какими будут значения свободных переменных в лямбда-выражении»? Ответом при неглубокой привязке будет «какими бы ни были динамические значения этих переменных при последующем вызове функции. Ответом при глубокой привязке будет «какими бы ни были динамические значения этих переменных при вычислении лямбда-выражения».
Тогда нам не пришлось бы рассматривать «функции, передаваемые в качестве аргументов». Все «функции, передаваемые в качестве аргументов» выглядят странно, потому что что происходит, когда вы передаете функцию в качестве параметра (захватывая ее динамическую среду), и все, чему вы ее передаете, затем передает ее куда-то еще? Должна ли динамическая среда восстановиться?
Связанные вопросы и ответы
person
Joshua Taylor
schedule
03.12.2015
(define (f z) ...
немного неверна. Он должен закончиться после(display (+ z y))
, верно? - person Joshua Taylor   schedule 03.12.2015let
устанавливает здесь динамические привязки)(let ((x 10)) (let ((y (lambda () x))) (let ((x 12)) (display (y)))))
. При неглубокой привязке вы увидите 12, а при глубокой привязке — 10, потому что динамическая среда, которая была активной при оценке лямбда-выражения, привязала x к 10. - person Joshua Taylor   schedule 17.12.2015