Поверхностное и глубокое связывание

Я пытался понять концепцию динамической/статической области действия с глубокой и мелкой привязкой. Ниже приведен код-

(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)

Я понимаю, что при динамической области видимости значение вызывающей функции используется вызываемой функцией. Итак, используя динамическую привязку, я должен получить ответ - 110. Используя статическую область видимости, я бы получил ответ 0. Но я получил эти результаты без учета поверхностной или глубокой привязки. Что такое поверхностное и глубокое связывание и как оно изменит результат?


person Noober    schedule 02.12.2015    source источник
comment
этот вопрос, Dynamic Scoping - Deep Binding vs Shallow Binding полезен? Между прочим, это был первый результат поиска в Google по запросу «глубокая привязка с мелкой привязкой». Вторым был Shallow & Deep Binding — что будет печатать эта программа?   -  person Joshua Taylor    schedule 03.12.2015
comment
Я слышал о динамической и лексической области видимости, но не припомню, чтобы когда-либо слышал о поверхностной и глубокой привязке. И я здесь уже довольно давно, так что не могу представить, что я один такой. Может быть, вопрос может дать некоторую ссылку на то, откуда берутся концепции?   -  person Luis Casillas    schedule 03.12.2015
comment
@LuisCasillas На самом деле у меня была такая же реакция, но быстрый поиск в Google довольно быстро дает некоторые значимые результаты. Я не думаю, что перефразирование этих концепций действительно является задачей ОП. (Я говорю это после того, как мне тоже пришлось искать эти термины. Я просто думаю, что они упоминаются в достаточном количестве примечаний к курсу и т. Д., Что ОП не нужно объяснять их больше, чем ОП нужно объяснять лексические и динамические обязательно для нас.)   -  person Joshua Taylor    schedule 03.12.2015
comment
Ваша вложенность в (define (f z) ... немного неверна. Он должен закончиться после (display (+ z y)), верно?   -  person Joshua Taylor    schedule 03.12.2015
comment
@JoshuaTaylor, этот ответ неверен, как и другие.   -  person Will Ness    schedule 17.12.2015
comment
куча релевантных результатов появляется при поиске схемы перерутирования   -  person Will Ness    schedule 17.12.2015
comment
@WillNess Это был не тот термин, о котором я слышал раньше (тем более, что один из вариантов настолько необычен в реальных языках программирования). Я не думаю, что ответы на другие вопросы были хорошими, но правильно ли я понял их в своем ответе?   -  person Joshua Taylor    schedule 17.12.2015
comment
@JoshuaTaylor еще не читал, это длинно. Мой связанный ответ короткий, может быть, вы могли бы прочитать его и сказать мне, совпадает ли он. :) и ответ на 20 голосов тоже совершенно неверен.   -  person Will Ness    schedule 17.12.2015
comment
@WillNess В своем ответе вы говорите, что программа не сможет заметить разницу между поверхностной и глубокой привязкой, но множество ресурсов в Интернете говорят, что существуют наблюдаемые различия. Насколько я могу судить, разница в том, что когда лямбда-выражение оценивается для создания объекта функции, глубокая привязка дает объекту функции постоянную ссылку на текущую динамическую среду (для разрешения переменных с динамической областью), тогда как при поверхностной привязке ( более распространенный вариант), объект функции ссылается на динамическую среду, которая активна, когда...   -  person Joshua Taylor    schedule 17.12.2015
comment
они означают лексический и динамический. их использование поверхностной и глубокой терминологии просто неправильно. глубокая привязка — это когда у вас есть цепочка на env. кадры. мелкой является когда у вас есть только один кадр.   -  person Will Ness    schedule 17.12.2015
comment
... вызывается этот функциональный объект. Таким образом, определенно будут заметные различия. Например, если вы это сделаете (при условии, что let устанавливает здесь динамические привязки) (let ((x 10)) (let ((y (lambda () x))) (let ((x 12)) (display (y))))). При неглубокой привязке вы увидите 12, а при глубокой привязке — 10, потому что динамическая среда, которая была активной при оценке лямбда-выражения, привязала x к 10.   -  person Joshua Taylor    schedule 17.12.2015
comment
Давайте продолжим обсуждение в чате.   -  person Will Ness    schedule 17.12.2015


Ответы (1)


В этих конспектах лекций есть пример 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
comment
Я нашел книга 1997 по Perl, в которой также используется эта терминология. реальная проблема заключается в времени создания замыкания; выбор одной дисциплины делает другую недоступной для программиста; реальным решением было бы сделать это явным, как close_over((x,y,...),proc_name) или что-то в этом роде, поэтому программист передал бы либо это, либо просто голое proc_name в качестве funarg (функционального аргумента). Как мы говорили в чате, в LISP это делалось с помощью (function (lambda(... или просто lambda. - person Will Ness; 29.12.2015
comment
и еще одна книга 2015 года (1-е изд. 2005 г.), в которой содержится псевдокод, на который вы ссылаетесь! :) (на стр. 152-153). ((Я все еще думаю, что это просто неправильно и неправильное использование терминологии. :) )) - person Will Ness; 29.12.2015