звонок / cc в Lua - возможно?

В статье Википедии о продолжении говорится:
«На любом языке, который поддерживает замыкания < / strong>, можно писать программы в стиле продолжения передачи и вручную реализовать call / cc. "

Либо это правда, и мне нужно знать, как это сделать, либо это неверно, и это утверждение необходимо исправить.

Если это правда, пожалуйста, покажите мне, как реализовать call / cc в Lua, потому что я не понимаю, как это сделать.

Думаю, я можно было бы реализовать call / cc вручную, если бы в Lua была функция coroutine.clone, как описано здесь.

Если замыканий недостаточно для реализации call / cc, что еще нужно?

Приведенный ниже текст является необязательным.
P.S .: Lua имеет одноразовые продолжения с таблицей сопрограмм. Функция coroutine.clone позволила бы мне клонировать ее, чтобы вызывать ее несколько раз, что фактически делает возможным call / cc (если я не понимаю call / cc). Однако этой функции клонирования в Lua нет. Кто-то на IRC-канале Lua предложил мне использовать библиотеку Pluto (она реализует сериализацию) для маршалинга сопрограммы, ее копирования, а затем демаршалинга и повторного использования. Хотя это, вероятно, сработает, меня больше интересует теоретическая реализация call / cc и определение фактического минимального набора функций, которые должен иметь язык, чтобы можно было реализовать его вручную.

РЕДАКТИРОВАТЬ 1: Хорошо, люди, помогите мне здесь, это заняло у меня много времени, потому что я не знаю никакой схемы, но я придумал кое-что, что должно нам помочь. Пожалуйста, посмотрите на коды ниже. Первая - это программа на Scheme, вторая - та же программа, но на Lua.
Надеюсь, это нам поможет. Я считаю, что мы очень близки.

PS: Эти примеры взяты из первого примера на статья в Википедии о CallCC. Версия схемы

(define call/cc call-with-current-continuation)

; callcc CPS-transformed (thanks to the people from the #scheme channel at freenode.net)
(define cpscallcc
  (lambda (consumer k)
    (let ((cc (lambda (result) (k result))))
      (consumer cc k))))

; this is the continuation we will use to display the "returned" values
(define main-continuation
  (lambda (result)
    (display "--> ")
    (display result)
    (newline)))

; define f function non-CPS
(define (f return)
  (return 2)
  3)

; these are my past attempts at defining a CPS f function
;(define (cps-f return k)
;  (k (return 2)) 3)
;(define (cps-f return k)
;  (k (lambda ()
;       (return 2)
;       3)))

; this is what I came up with - I'm not sure if this is correctly CPS-transformed but I     believe so
(define (cps-f return k)
  (return 2)
  (k 3))

; call the non-CPS f function
(display (f (lambda (x) x))) ; displays 3
(newline)

; call the non-CPS f function with call/cc (I don't understand what this does)
(display (call/cc f)) ; displays 2
(newline)

; now call the CPS version of the f function
(cps-f (lambda (x) x) main-continuation)  ; displays --> 3

; now call the CPS version of the f function with the CPS version of call/cc
(cpscallcc cps-f main-continuation)  ; displays --> 2 but then it also displays --> 3 afterwards -> I'm not sure why it displays the 3 afterwards, as it should only display the 2 just like the non-CPS versions above



Версия Lua

-- callcc CPS-version
cpscallcc = function(consumer, k)
    local cc = function(result)
        return k(result)  -- ?or k(result)
    end
    return consumer(cc, k)  -- ?or return consumer(cc,k)
end

-- define f function non-CPS
f = function(ret)
    ret(2)
    return 3
end

-- define f function CPS-version (again, not sure this is correct)
cps_f = function(ret, k)
    ret(2)
    k(3)
end

-- call the non-CPS f function
print(f(function(x) return x end))

-- we cant call the non-CPS f function with callcc because
-- Lua doesnt have callcc, but the line below displays the correct expected output (maybe by accident)
--cpscallcc(f, print)

-- now call the CPS version of the f function
cps_f( function(x) return x end, print )  -- displays 3

; now call the CPS version of the f function with the CPS version of call/cc
cpscallcc( cps_f, print) -- displays 2 and then 3 just like the Scheme version!!
-- so apparently the translation from Scheme to Lua is correct...



Я использую DrScheme и Lua для Windows - для всех, кто хочет помочь, это два простых для загрузки и установки инструмента, которые просто работают.


person PeterM    schedule 13.05.2010    source источник


Ответы (6)


Есть два предварительных условия для ручной реализации call / cc в соответствии с цитатой из Википедии:

  1. язык должен поддерживать закрытие
  2. вы должны писать свою программу в стиле продолжения передачи (CPS)

Подозреваю, что №2 вам не понравится.

Чтобы написать вашу программу в стиле перехода «продолжение»:

  1. Каждая функция должна принимать аргумент продолжения
  2. Функции должны возвращаться, вызывая их продолжение

Итак, используя k в качестве имени аргумента продолжения, функция будет выглядеть так:

function multiplyadd(k, x, y, z) return k(x * y + z) end

Верхний уровень может использовать print в качестве продолжения, поэтому вызов multiplyadd на верхнем уровне будет выглядеть так:

multiplyadd(print, 2, 4, 1)

С помощью этого каркаса мы могли бы определить call / cc как

function callcc(k,f) return f(k,k) end

Обратите внимание, что вышеупомянутый multiplyadd на самом деле читы, поскольку * и + не находятся в CPS. Очень утомительно добавлять все операторы в форме CPS, заменять все функции библиотеки Lua эквивалентами CPS и переводить / генерировать весь ваш код в CPS; см. подробности здесь.

person Doug Currie    schedule 13.05.2010
comment
Ух ты, ты очень близко меня к пониманию. Не могли бы вы расширить определение функции callcc? В частности, объясняя часть, которая позволяет ему запоминать сохранение / запоминание всего состояния, как это делает Scheme call / cc. - person PeterM; 13.05.2010
comment
Я думаю, у меня возникли проблемы с использованием этой функции callcc - в Scheme вы должны указать место для перехода call / cc, но в CPS вы этого не делаете ... я думаю, это сбивает меня с толку. - person PeterM; 13.05.2010
comment
Поскольку k - это замыкание, к которому будет возвращаться callcc, передача его аргументу f выполняет задание call / cc [я только что отредактировал свое определение выше, так как я забыл вернуть его должным образом]. Когда программа находится в CPS, это тривиально, потому что продолжение всегда доступно; в Scheme он скрыт, и задача call / cc сложнее (на уровне языка реализация может быть столь же тривиальной в компиляторе), поскольку она должна реифицировать продолжение. - person Doug Currie; 13.05.2010
comment
Хорошо, теперь ваше определение callcc эквивалентно определению Scheme? Это означает, что это функция, которая захватывает продолжение, чтобы я мог вызвать его позже? Мне всегда казалось, что имя callcc вводит в заблуждение (по крайней мере, в отношении того, как оно используется в Scheme). - person PeterM; 14.05.2010
comment
Да, callcc эквивалентен Scheme, за исключением того, что продолжение является явным аргументом (поскольку программа находится в CPS), а не неявным аргументом (поскольку Scheme использует прямой стиль). - person Doug Currie; 14.05.2010
comment
действительно ли callcc возвращает k (f (k)) или возвращает f (k (k))? Я не могу найти никаких ссылок на первое, но я нашел несколько ссылок на второе: комментарий Калани здесь: blog.lab49.com/archives/893 и вторая ссылка PDF (принципы языков программирования) на странице результатов Google, которую вы получаете при поиске callcc fk (с кавычками). - person PeterM; 18.05.2010
comment
пожалуйста, замените указанное выше или оно вернет f (k (k))? для этого текста: или это (теперь обозначение haskell) callcc f k = (f k) k (то же самое, что callcc f k = f k k)? - person PeterM; 18.05.2010
comment
@Pessimist, да, функции f нужно и продолжение, и аргумент; Я думал о гибриде прямого и CPS (вероятно, потому, что я использовал операторы, отличные от CPS, в примере multiplyadd). Значит, это должно быть return f (k, k) - я отредактировал свой ответ. Конечно, все это условность. Любая функция в CPS может получить свое продолжение от своего первого аргумента, поэтому callcc является избыточным / ненужным. - person Doug Currie; 18.05.2010

Я думаю, вы забыли о том, как писать свою программу в стиле продолжения. Как только вы это сделаете, call / cc станет тривиальным (в Lua или на любом другом языке), так как продолжение будет явным параметром для всех функций (включая call / cc).

PS: помимо замыканий, вам также нужны правильные хвостовые вызовы для программирования в стиле передачи продолжения.

person Roberto Ierusalimschy    schedule 13.05.2010
comment
Прежде всего, для меня большая честь ответить на мой вопрос. Я полностью влюблен в ваш язык по многим причинам (небольшое ядро, интеграция с C, очень последовательная грамматика, реальные замыкания, функции первого класса и т. Д.). Теперь к ответу: этот PS многое проясняет. Было бы невежливо, если бы я попросил пример реализации call / cc в Lua, в котором Lua допускает как закрытие, так и оптимизацию хвостового вызова? :) - person PeterM; 13.05.2010
comment
Я согласен с приведенным ниже ответом Нормана Рэмси. Думаю, лучше будет вопрос: есть ли планы по реализации первоклассных продолжений и, следовательно, callcc в Lua? ;) - person PeterM; 14.05.2010
comment
Хорошая точка зрения. Я отредактировал Википедию, чтобы уточнить правильные хвостовые запросы. Статья о хвостовой рекурсии ужасна ... - person Norman Ramsey; 15.05.2010

Отвечая на вопрос о планах call / cc в Lua: Планов на call / cc в Lua нет. Захват продолжения либо слишком затратен, либо требует некоторого анализа кода, выходящего за рамки того, что может сделать компилятор Lua. Также существует проблема, что продолжения Lua могут включать части в C.

Однако с помощью сопрограмм мы уже можем реализовать call / cc1 в Lua (одноразовые продолжения). Этого достаточно для многих случаев использования продолжений.

person Roberto Ierusalimschy    schedule 14.05.2010
comment
Я могу понять, что. Есть ли планы относительно coroutine.clone, вроде того, что реализовал Майк Полл по ссылке в моем исходном посте? - person PeterM; 15.05.2010
comment
Рабочий пример callcc в Lua с использованием модифицированного Lua, реализующего coroutine.clone, см. Здесь: github.com/torus/lua-call-cc/blob/master/callcc.lua. Я скоро попробую. :) - person PeterM; 15.05.2010

Ключевая фраза

Возможна реализация программ в стиле передачи продолжения

(Подчеркну мой.) Вы делаете это, беря обычные программы «прямого стиля» и конвертируя их в стиль передачи с продолжением (CPS) с помощью программного преобразования, называемого преобразованием CPS. Ключ в том, что преобразование CPS call/cc - простая функция.

Это непрактично для программистов. Преобразование CPS имеет два применения:

  • Как теоретическая идея для изучения особенностей языка, особенно операторов управления
  • Как проход в компиляторе, который использует CPS в качестве промежуточного языка

Вы не хотите приближаться к выполнению CPS-преобразований в коде Lua, особенно вручную.

person Norman Ramsey    schedule 13.05.2010
comment
Возможно, вы не захотите писать целые приложения с использованием CPS, но вполне практично реализовать отдельные алгоритмы для решения подзадач таким образом. - person sigfpe; 18.05.2010
comment
CPS-преобразование call / cc - это простая функция - Осторожно! call / cc может быть определен в цели преобразования CPS с помощью простой функции, но это не преобразование CPS чего-либо, поскольку оно не возвращается, вызывая свое продолжение. - person Charles Stewart; 22.03.2012
comment
Вопрос был в том, «как», а не «должен». - person Lloyd Moore; 07.12.2016

это возможно: компилятор TypeScript-to-Lua https://github.com/roblox-ts/roblox-ts + call-cc в JS https://github.com/zaoqi/callcc.js/blob/master/callcc.js

person zaoqi    schedule 29.07.2019
comment
Ссылка на решение приветствуется, но убедитесь, что ваш ответ полезен и без нее: добавьте контекст вокруг ссылки, чтобы ваш коллега пользователи будут иметь некоторое представление о том, что это такое и почему это есть, а затем процитировать наиболее релевантную часть страницы, на которую вы ссылаетесь, в случае, если целевая страница недоступна. Ответы, которые представляют собой не более чем ссылку, могут быть удалены.. - person Alessio; 29.07.2019

Вот моя схема cps-convert in, просто передайте ей каждую функцию, которую хотите преобразовать.

(define (cps-convert function . functions)
  # Since "help" is called at 2 different places...
  (define (help) (error "syntax: (cps-convert f1 f2 ...)"))
  # Single function converter
  (define (convert func)
    # "name" contains the function's name prefixed with "cps-"
    (let ([name (string->symbol
                          (string-append "cps-" (symbol->string func)))])
      # Dirty hack to define "cps-*" in the global environment
     `(eval '(begin
                   # Necessary to prevent the function from being evaluated
                   (define ,name #f)
                                # Magic
                   (set! ,name (lambda (k . args) (k (func args)))))
                 # Global environment
                 (interaction-environment))))
  # Prerequisite... Call help if condition not met
  (if (symbol? function)
      # function is a symbol
      (cond
        # If there is only one function to convert
        [(null? functions) (convert function)]
        # Else ensure every other "functions" are symbols and convert each
        [(every symbol? functions) (apply convert function functions)]
        # Oops! Condition not met!
        [else (help)])
      # Else clause from previous "if"
      (help)))
person Samuel Duclos    schedule 21.11.2011
comment
Это не имеет отношения к вопросу. - person Lloyd Moore; 07.12.2016