Pull to refresh
7
0
Георгий Корепанов @gkorepanovgk

Пользователь

Send message

Спасибо, прекрасная статья для ознакомления с совеременным состоянием DL (и отдельное спасибо за ссылки)!
Ждем вторую часть (надеюсь, техническую и понятную, а то сейчас все больше поверхностных обзоров успешных архитектур и проектов, а не статей с серьезным их описанием и анализом).

Scheme — это мощный язык. Не совсем правильно говорить о возможности «нескольких продолжений». Когда интерпретатор встречает call/cc, он просто в явном виде даёт в руки программисту текущее состояние, передавая его как аргумент в лямбда-функцию. Дальше — дело программиста, что с этим состоянием сделать. Распространённой практикой является сохранить его, как описано в статье.

И, конечно, ничего не мешает вам рассовать call/cc по разным местам программы и сохранять состояния в разных переменных. Это просто переменные, такие же, как и любые другие. Тот факт, что в каждой них сохранён некоторый контекст — это воля программиста.
Есть предложения, о чём написать? Или вы имеете в виду подробное описание макросов?
К сожалению, ничего не слышал о meep, mpb; ознакомлюсь, если будет время.

Да, с одной стороны, есть некоторая схожесть. Хотя у меня gsave/grestore вызывают скорее ассоциацию с обычными функциями в процедурном программировании. Позвали функцию — она сделала изолированно свою работу независимо от нас, ничего не поменяв (только вывела что-то на экран). Понятно, что функция — значительно более простая конструкция, чем call/cc.
Спасибо! Я только сейчас понял, что наличие (display ...) лишь усложняет восприятие. Добавил информацию в статью.
Сожалею, что так и не смог помочь. Общий принцип работы интерпретатора достаточно прост, совпадает с логикой работы интерпретатора любого функционального языка (в общих чертах).

Представьте, что я не рассказывал бы о продолжениях вообще:

(define test-func 
    (if (value) "true " "false "))
(display test-func)

(sample-function)
(newline)


Сначала распечатается результат test-func, затем выполнится sample-function. Программа завершит свою работу.

Теперь же вернёмся к нашему случаю (кажется, я совершил ошибку, обернув (*env* #t) никогда больше не осуществляющимся вызовом (display) — это лишь затруднило понимание):

(define test-func 
    (if (call/cc (lambda (cc) 
                  (begin
                   (set! *env* cc)
                   (> 3 5))
                 )) "true " "false "))
(display test-func)

(*env* #t)
(newline)


При вызове (*env* #t), грубо говоря, весь существующий код исчезает. Мы попадаем в такую программу:

(define test-func 
    (if (#t) "true " "false "))
(display test-func)

(*env* #t)
(newline)


Она стала совсем простой! Выполняется этот код обычным образом. Потом доходит до (*env* #t) и этот код снова уничтожается, заменившись, правда, таким же. Так и получается «бесконечный цикл»!
Когда мы совершаем прыжок на сохранённое состояние, мы, по сути, уничтожаем всё (кроме, конечно, переменных типа самой *env*), с чем работали, оказываясь в новой программе (часто пытаются описать это параллельной реальностью, прыжком во времени, и т.п.; я сознательно пострался избегать этого в статье, чтобы не создавать ложных ассоциаций, а попытаться донести, что же происходит на самом деле). Уничтожается стек вызова, подменяется тем стеком, который мы сохранили ранее. При этом с собой мы можем забрать один единственный кусок value, который «подменит» в «новой программе» часть кода, которая была обёрнута call/cc констуркцией.
Откуда появилось (display (*env* #t))?
Такого в коде нет. Возрат к сохранённому состоянию осуществляется строкой кода (*env* #t). В этот момент мы, по сути, начинаем выполнять «другую» программу, сохранённую в *env*. Её «текст» выглядит так:

(define test-func 
    (if (value) "true " "false "))
(display test-func)

(display (*env* #t))
(newline)


В этом «коде» value — это то значение, которое мы передали при вызове:

(*env* value)


Поскольку в нашем случае value — это #t, то фактически исполняться будет всё вот так:

(define test-func 
    (if (#t) "true " "false "))
(display test-func)

(display (*env* #t))
(newline)


if, разумеется, вернёт строчку "true ", она и будет распечтана. после этого интерпретатор пойдёт дальше по тексту, снова встретив (*env* #t)
Попытаюсь рассказать поподробнее. На самом деле всё очень просто, но сложно формулируется на естественном языке.

Рассмотрим процесс интерпретации кода из соответствующего примера.
(define test-func 
    (if (call/cc (lambda (cc) 
                  (begin
                   (set! *env* cc)
                   (> 3 5))
                 )) "true " "false "))
(display test-func)

(display (*env* #t))
(newline)


Начинается всё с вызова функции (display ...), которая вызывает test-func. Далее, test-func вызывает вычисление блока (if ...). Теперь, внимание, внутри условия if-a мы сохраняем текущее состояние с помощью (set! *env* cc). Т.е. теперь, если мы когда-нибудь вызовем (*env* value), то мы окажемся именно тут (****), «внутри» условия if-a. Но вместо вычисления этого условия будет подставлено значение value. Запомним это, двигаемся дальше.

test-func спокойно завершит свою работу ((> 3 5) вернёт #f, if вернёт "false ", ну а test-func вернёт этот результат выше). display получит этот результат, распечатает его на экране и тоже завершит свою работу.

Далее вызывается (*env* #t). Мы переносимся в состояние ****! Всё идёт по-новой, точно так же, за тем лишь исключением, что условие if-a вычислено не будет, вместо него будет подставлено #t и поэтому в конце-концов будет распечатано "true ".

А потом снова будет (*env* #t)

Честно — не знаю, как объяснить по-другому, но если всё-таки будет непонятно, я напрягусь и придумаю что-нибудь ещё.

Удачи вам в изучении Scheme!

В рунете, пожалуй, действительно информации немного.
Хорошо, обязательно будет статья!

Information

Rating
Does not participate
Registered
Activity