LINUX.ORG.RU

лексическая область видимости

 


1

3

Небольшая непонятка.

Когда мы говорим о лексической области видимости мы как-бы подразумеваем следующее: функция «запомнила» значения переменных из всей цепочки замыканий на момент определения. Когда она будет вызвана, она не будет видеть текущий контекст, а брать значения переменных из того контекста в котором была определена. Но что же происходит в следующем коде:


(define a 1)
(define tst (lambda() (write a)))
(define a 10)
(tst)

10
Почему функция берет значение а из контекста вызова а не определения? Мы имеем, по факту динамический биндинг?



Последнее исправление: anonimous (всего исправлений: 4)

Хех. Забавно :)

$ php -a
php > $x = 10;
php > $f = function() use($x) { print $x; };
php > $x = 20;
php > $f();
10
php > $f2 = function() use(&$x) { print $x; };
php > $x=30;
php > $f2();
30

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

Не знаю PHP, но я так понял из вашего кода, что без явного указания (амперсанд) в PHP на момент вызова связывание лексическое?

anonimous
() автор топика
Ответ на: комментарий от anonimous

Я не в курсе общепринятых терминов на счёт замыканий, но в первом случае в функцию передаётся переменная по значению, во втором — по ссылке. Т.е. поведение явно указывается.

Интересно, если в Лиспе в функции переменную изменить, она в текущем на момент вызова контексте изменится? Если да, то в Лиспе в замыкание просто переменная по ссылке передаётся.

Ещё бы интересно в JS проверить, там замыкания — постоянно используемый механизм, но интерактивного нет под рукой, а лепить HTML-страничку лениво, спать уже иду :)

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

Ещё бы интересно в JS проверить

В JS то же самое, что в Scheme.

anonimous
() автор топика
Ответ на: комментарий от KRoN73

Если переменнную изменить в данном случае, мы переопределим глобальную переменную. А если Вы имеете в виду, определение собственной переменной отличной от глобальной, то в момент вызова она будет связывать со своей конечно.

anonimous
() автор топика

Почему функция берет значение а из контекста вызова а не определения?

Здесь один контекст, и одна единственная «а». Второе определение не вводит нового связывания.

anonymous
()
Ответ на: комментарий от anonimous

Здесь контекст определения и контекст вызова.

И они тут совпадают.

anonymous
()
Ответ на: комментарий от KRoN73

Интересно, если в Лиспе в функции переменную изменить, она в текущем на момент вызова контексте изменится

Изменится. Поэтому можно делать ООП-на-замыканиях.

(define (make-obj)
  (define x 0)
  (values (lambda () x)
          (lambda (new-x) (set! x new-x))))

(define-values (get-x1 set-x1) (make-obj))
(define-values (get-x2 set-x2) (make-obj))

(get-x1) ; 0
(set-x1 2)
(get-x1) ; 2
(get-x2) ; 0 -- этот x отдельный

monk ★★★★★
()
Ответ на: комментарий от anonimous

Что значит не вводит.

Значит не вводит.

(define x ...)
bla-bla ...
(define x ...)
bla-bla2
это нет
(define x ...)
bla-bla ...
(define (let ((x ...)) bla-bla2))
а
(define x ...)
bla-bla ...
(set! x ...)
bla-bla2

anonymous
()
Ответ на: комментарий от anonymous
(define x ...)
bla-bla ...
(define (let ((x ...)) bla-bla2))

не так а так, конечно:

(define x ...)
bla-bla ...
(let ((x ...)) bla-bla2)

anonymous
()

Когда мы говорим о лексической области видимости мы как-бы подразумеваем следующее: функция «запомнила» значения переменных из всей цепочки замыканий на момент определения.

Это откуда такое определение? Лексическая область — значит, что ссылка на переменную определяется местом видимости. То есть при опредении видна переменная a. И даже если в момент выполнения будет другая a, то значение будет браться из той, что была видна на момент поеределения:

(define a 1)
(define tst (lambda() (write a)))
(let ((a 10))
   (tst))

1
monk ★★★★★
()
Ответ на: комментарий от monk

функция «запомнила» значения

Не значения а ссылку на окружение. Посмотри пример реализации интерпретатора Scheme в SICP.

Почему функция берет значение а из контекста вызова а не определения?

Она и берет из контекста определения

R5RS:

5.2.1 Top level definitions

At the top level of a program, a definition

(define <variable> <expression>)

has essentially the same effect as the assignment expression

(set! <variable> <expression>)

if <variable> is bound. If <variable> is not bound, however, then the definition will bind <variable> to a new location before performing the assignment, whereas it would be an error to perform a set! on an unbound variable.

(define add3
  (lambda (x) (+ x 3)))
(add3 3)                                    ===>  6
(define first car)
(first '(1 2))                              ===>  1

Some implementations of Scheme use an initial environment in which all possible variables are bound to locations, most of which contain undefined values. Top level definitions in such an implementation are truly equivalent to assignments.

как и писал анонимус выше.

korvin_ ★★★★★
()
Ответ на: комментарий от monk

Вы сказали ровно то же что и я, только другими словами.

anonimous
() автор топика
Ответ на: комментарий от korvin_

Теория, это конечно хорошо, но по факту, на момент определения tst, переменная a не связана со значением 10. Значит, функция tst получает его в момент вызова. А это - динамическое связывание.

anonimous
() автор топика
Ответ на: комментарий от anonimous

Теория, это конечно хорошо, но по факту, на момент определения tst, переменная a не связана со значением 10.

То, с каким значением связана переменная a на момент определения tst, не играет ни какой роли. Как уже сказал korvin_, tst запоминает не само значение, а ссылку на (ближайшее) окружение. Твой пример для не top-level окружения аналогичен такому:

(let ((a 1))
  (define (tst) (displayln a))
  (set! a 10)
  (tst))

А вот как действует dynamic scoping. CL:

(defvar a 1)

(defun tst ()
  (format t "~a~%" a))

(let ((a 10))
  (tst)) ; => 10

(format t "~a~%" a) ; => 1

Racket (SRFI 39):

#lang racket

(define a (make-parameter 1))

(define (tst) (displayln (a)))

(parameterize ([a 10])
  (tst)) ; => 10

(displayln (a)) ; => 1

theNamelessOne ★★★★★
()
Ответ на: комментарий от anonimous

А это - динамическое связывание.

Что такое, по-твоему, динамическое связывание? Мне просто кажется, что мы с тобой говорим о разных вещах.

theNamelessOne ★★★★★
()

Такой номер в F# и Haskell не прошел бы. Там были бы нужны явные ссылки, поскольку замыкание в этих двух языках всегда происходит по значению.

dave ★★★★★
()
Ответ на: комментарий от dave

поскольку замыкание в этих двух языках всегда происходит по значению.

Просто в Haskell нет деструктивных модификаций, и разницы между замыканием по значению и по ссылке нет.

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 1)
Ответ на: комментарий от theNamelessOne

Просто в Haskell нет деструктивных модификаций

Всякие IORef не рассматриваем.

theNamelessOne ★★★★★
()

функция «запомнила» значения переменных

Запомнила адрес переменной, а значение может меняться.

Опять этот лупень в Development срет тупняком... перенесите тему в толксы плиз.

TDrive ★★★★★
()
Ответ на: комментарий от theNamelessOne

Динамическое связывание, по-моему, это позднее связывание, когда функция связывает переменные из своего скопа со значениями из текущего скопа в рантайме. Может ошибаюсь, конечно, я не профи, только учусь. Но подозрительно это все выглядит как то. Все таки везде жужжат, что замыкания забирают лексический контекст, а здесь мы наблюдаем несколько иное. Какое то тут противоречие интуитивное, не могу объяснить. Иными словами, наша tst, в данном случае, ведет себя точно также как в вашем примере с CL, забирает значения из того контекста, в котором вызвана. То что define в toplevel ведет себя деструктивно, я знаю. Но это сути не меняет. Ломается концепция лексического скопа. Она уже с примесями какими-то, не такая чистая, что-ли становится.

anonimous
() автор топика
Ответ на: комментарий от theNamelessOne

Вообще хрен знает, как это все понимать. Даже и в этом примере, непонятно, что происходит.

(let ((a 1))
  (define (tst) (displayln a))
  (set! a 10)
  (tst))
Вроде бы в scheme энергичные вычисления, значит в момент определения tst в теле ее у нас уже 1, никакого имени там нет. Тогда почему при вызове, она опять обращается по имени, ищет биндинг и вычисляется в 10. Ведь никакого а там уже нет на момент определения. Бред какой то.

anonimous
() автор топика
Ответ на: комментарий от korvin_

Да у меня каша, я не спорю. Но как это все понять, я хз, нет тут концептуальной чистоты в этих замыканиях, по-моему. Гвоздями все прибито. Костыли. Я уже с ума схожу.

anonimous
() автор топика
Ответ на: комментарий от anonimous

Да у меня каша, я не спорю. Но как это все понять, я хз

Я тебе написал как — почитай SICP например.

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

Ну да, здесь ступил кажется, свободная переменная не будет вычисляться до вызова. Это да. Она бы вычислилась если бы мы ее явно передали в качестве аргумента, т.е «связали». Здесь да, ступил, каюсь.

anonimous
() автор топика
Ответ на: комментарий от anonimous

Динамическое связывание, по-моему, это позднее связывание, когда функция связывает переменные из своего скопа со значениями из текущего скопа в рантайме.

Немного не то. Позднее связывание — это механизм поиска вызванного метода класса в рантайме. В большинстве динамически-типизированных языков (в том числе и в лиспе) позднее связывание.

http://en.wikipedia.org/wiki/Late_binding

Lexical scoping vs dynamic scoping (не binding!) — совсем другой вопрос. Рассмотрим модель окружений.

Окружение — это набор привязок (пар символ — значение)), имеющий ссылку на объемлющее окружение (кроме окружения верхнего уровня). Каждому выражению неявно соответствует окружение, в котором это выражение определено. При вычислении значения переменной сначала идёт поиск в соответствующем окружении, затем (если в этом окружении привязки не найдено) в родительском, затем в родительском родительского и т.д., пока не дойдём до окружения верхнего уровня. Если и нём привязки не найдено, то получаем ошибку «Undefined/Unbound variabled».

Некоторые формы (такие как let) могут создавать привязки в новом окружении. Новое окружение добавляется (и удаляется) в порядке FIFO.

Пример.

;; top-level environment
(define a 0)

(let ((a 1)
      (b 2))
  ;; let #1
  (let ((a 10))
    ;; let #2
    (+ a b)))

Окружения для этого кода:

      let #2         let #1        toplevel
    +--------+     +--------+     +--------+
    | a -> 10|     | a -> 1 |     | a -> 0 |
    +--------+     +--------+     +--------+
    |        |     | b -> 2 |     |        |
    +--------+     +--------+     +--------+
    |........|     |........|     |........|
    |.......-+---->|.......-+---->|........|
    +--------+     +--------+     +--------+

Это всё касалось лексической области видимости. Если в язык добавить переменные с динамической областью видимости, то ситуация немного изменяется. Рассмотрим для примера CL.

Формы связывания (let) и поиск значения переменной для лексических переменных в CL ведут себя как описано выше. Но кроме лексических переменных в CL есть ещё и динамические, или специальные (special), переменные. С каждой такой переменной связан глобальный стек её значений, и поиск значений для такой переменной ищется именно в этом стеке, а не в соответствующем окружении, и let добавляет значения (привязки) именно к этом стеку.

;;; специальная переменная `a`
;;; стек значений `a`: (3)
(defvar a 3)

(defun tst ()
  ;; т.к. `a` - специальная переменная,
  ;; её значение ищется в стеке `a`
  (format t "~a~%" a))

(let ((a 4))
  ;; let в данном случае создаёт пустое окружение,
  ;; а к стеку `a` добавляется 4. стек `a`: (4 3)
  (tst)) ; -> 4

(tst) ; -> 3
theNamelessOne ★★★★★
()
Ответ на: комментарий от anonimous

Не люблю я лиспоязыки. В спп всё значитально проще:

int main()
{
  int a =1;
  auto f1 = [=] () {printf("%d\n", a);};
  auto f2 = [&] () {printf("%d\n", a);};
  a = 10;
  f1();
  f2();
}
А в эрланге такого даже не может быть:
start() ->
  apply(i(), []).

i() ->
  A = 1,
  fun() -> io:format("~w~n", [A]) end.

nanoolinux ★★★★
()
Последнее исправление: nanoolinux (всего исправлений: 1)
Ответ на: комментарий от monk

Поэтому можно делать ООП-на-замыканиях.

А ещё можно удалять зубы через жопу, надевать трусы через голову, забивать гвозди микроскопом и охотиться на слона с рогаткой, а на мышей с RPG.

Почему бы вместо этого не использовать нормальный современный язык с прямой, не костыльной реализацией ООП?

anonymous
()
Ответ на: комментарий от nanoolinux

Не люблю я лиспоязыки. В спп всё значитально проще

Ну ты попал! Тебя же сейчас здесь с говном съедят. Ты что, разве не в курсе, что Development — цитадель илитной маргинальщины всея Рунета? Маргинальнее, наверное, только /ц/ харкача. Сейчас тебе пояснят по понятиям, какой ты невежественный, недалёкий быдлокодеришка. И даже эрланг тебя не спасёт.

anonymous
()
Ответ на: комментарий от anonymous

Почему бы вместо этого не использовать нормальный современный язык с прямой, не костыльной реализацией ООП?

ООП во всех языках как раз и реализована через замыкания. Никакого существенно другого способа реализации еще не придумали.

anonymous
()
Ответ на: комментарий от anonymous

Если переписать тот пример на Common Lisp (с defun), то напечатается 100. В Scheme с toplevel define — тоже.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Ну, я так и думал. Там всегда замыкание создается по значению. Но если мы хотим замкнуть переменную, то тогда мы должны явно создать ссылку ref. Сама ссылка тоже всегда передается по значению, но она уже содержит изменяемое поле. Просто вводится дополнительный уровень косвенности. В общем, гибридное решение: и нашим, и вашим.

dave ★★★★★
()
Ответ на: комментарий от anonymous

Почему бы вместо этого не использовать нормальный современный язык с прямой, не костыльной реализацией ООП

Потому что для разных задач нужны разные реализации ООП. И если в хорошем языке можно просто использовать разные библиотеки и иметь в одной задаче CLOS (с поддержкой мультиметодов, аспектов, пре- и пост- методов и т.д.), а в другой (в той же программе) — простое ООП с минимумом overhead'а.

А в ваших «нормальных современных» приходится на каждую реализацию ООП по языку писать: AspectJ, C++ Open Method, ...

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от anonymous

ООП во всех языках как раз и реализована через замыкания. Никакого существенно другого способа реализации еще не придумали.

Но ведь это ложь. В С++ ООП реализовано через диспетчеризацию по vtables.

anonymous
()
Ответ на: комментарий от monk

Потому что для разных задач нужны разные реализации ООП.
с поддержкой мультиметодов, аспектов, пре- и пост- методов

Скажите, а Вы пробовали разрабатывать ПО, а не дрочить вприсядку?

anonymous
()
Ответ на: комментарий от anonymous

Скажите, а Вы пробовали разрабатывать ПО, а не дрочить вприсядку

Для разработки вообще лучше 1С и COBOL :-)

Но для души чего-то красивого хочется...

monk ★★★★★
()
Ответ на: комментарий от theNamelessOne

Спасибо, капитан. Уже и вбросить не получается.

cdshines ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.