LINUX.ORG.RU

Существует ли Common Lisp с полнофункциональным call/cc?

 , ,


1

4

cl-cont видел: 1. delimited 2. требует перекомпиляции с ним всего, из чего используешь продолжения.

Видел хак для SBCL. С ограничениями: функции должны быть без параметров и связанных переменных.

Есть ли нормально работающая версия (может в расширениях у каких-нибудь коммерческих лиспов)?

★★★★★

вроде бы нет, но могу ошибаться.

ymn ★★★★★
()

Нет.

Нормальные континуации потребуют переписывания половины стандарта. Начать можно с unwind-protect.

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

Начать можно с unwind-protect.

«unwind-protect evaluates protected-form and guarantees that cleanup-forms are executed before unwind-protect exits, whether it terminates normally or is aborted by a control transfer of some kind.».

И что? Вызов продолжения — тоже «control transfer of some kind.». Ну а если кто-то через продолжение влазит внутрь unwind-protect, то он должен осознавать что делает: (unwind-protect (return 1) (return 2)) тоже хз что возвращает.

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

unwind-protect

Или можно тупо запретить вызов продолжений внутрь защищённой формы. Опять же, ничему не противоречит.

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

Love5an писал об этом.

Это как повод подходит (вспоминается Дейкстра с его «goto considered harmful»). При этом goto есть даже в Ада и C#.

Но выглядит как религиозное убеждение.

В Недостатки Racket (комментарий) анонимус утверждает, что оверхед от реализации call/cc на уровне компилятора достаточно мал (при любом раскладе на порядок меньше, чем при использовании cl-cont).

В конце концов, можно считать, что продолжения есть в C: http://en.wikipedia.org/wiki/Setcontext .

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

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

Сделай макроэкспанд того, во что превращается код после обработки cl-cont. Например:

(macroexpand 
 '(cl-cont:defun/cc list10 () 
     (loop for i from 1 to 10 collect i)))

И подумай, может ли получившийся крокодил быстро выполняться.

(cl-cont:defun/cc list-cc (n) (loop for i from 1 to n collect i))
(defun list-n (n) (loop for i from 1 to n collect i))

(time (dotimes (k 10000) (list-cc k) nil))
Evaluation took:
  26.321 seconds of real time

(time (dotimes (k 10000) (list-n k) nil))
Evaluation took:
  1.052 seconds of real time
monk ★★★★★
() автор топика
Ответ на: комментарий от monk

В принципе, я согласен на отсутствие продолжений, если в примитивах языка есть средства для создания генераторов (типа питоновского yield) и сопрограмм (например, как в Ада). Но в CL нет ни того, ни другого. Теоретически можно написать на тредах, но на практике, код который на (for (in-range ...)) запускает тред, а в случае вложенных циклов — несколько тормозить будет почти как cl-cont.

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

pygen

Там генератор существует только внутри foreach. Ни из (iter...) ни из (loop...) его вызвать нельзя. Даже нельзя по очереди брать элементы из двух генераторов.

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

using Arnesi CPS transformer.

Это тот же cl-cont. Со всеми его минусами.

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

сопрограмм на CL на основе chanl

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

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

какие в Аде средства для создания сопрограмм?

Ну не совсем сопрограммы, но use-case почти тот же:

https://en.wikibooks.org/wiki/Ada_Programming/Tasking#Rendezvous

Хотя про chanl выше указано верно, почти то же самое.

У решения на call/cc есть плюс по производительности за счёт того, что не плодятся потоки ОС.

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

какие в Аде средства для создания сопрограмм?

Ну не совсем сопрограммы,

:) Это нити фактически, причем обычно нити ОС.

но use-case почти тот же:

Ну, продолжения на нитях тебе не подошли.

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

А call/cc на нитях есть?

Я уверен, что можно смоделировать (концептуально, call/cc - это просто приостановка/возобновление процесса вычислений), но, ИМХО, теряется весь смысл call/cc - легковесность (нить - это ядерный ресурс, переключения медиируются ядром, нужен ядерный стек).

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

теряется весь смысл call/cc - легковесность

На фоне cl-cont что угодно будет легковесным. А с учётом массового использования cl-cont (weblocks, cl-fibers, cl-async) то как минимум, надо бы проверить производительность.

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

Я не знаю Лиспа, но вот это:

«process tens of thousands of requests/responses at the same time»

по-моему, ставит крест на идее использовать нити.

tailgunner ★★★★★
()
10 февраля 2015 г.

Ты не понял, лисп не предназначен для этого, он работает по-другому.

anonymous
()

нет конечно

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

Нарушает стандарт => невозможно

Библиотека cl-cont тоже нарушает стандарт? Или в каком смысле нарушает?

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

Библиотека cl-cont тоже нарушает стандарт?

в cl-cont нету полнофункционального call/cc, так тчо не нарушает

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

нету полнофункционального call/cc, так что не нарушает

Какой именно пункт стандарта нарушает полнофункциональный call/cc ?

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

Проблема даже не в нарушении стандарта, а в том, что неясно, как интерпретировать данную конструкцию в рамках модели образа. Что будет, если я вызову продолжение из макроса, например? Точнее, что вообще _должно_ быть?

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

Что будет, если я вызову продолжение из макроса, например? Точнее, что вообще _должно_ быть?

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

Продолжение технически ничем не отличается от замыкания. В случае undelimited вычисления из REPL оборачиваются в блок (handler-case ... (prompt (val) val)) и записываемое продолжение имеет форму (signal (make-prompt ...)).

То есть

(define cont #f)

(define (set-cont)
  (define n 1)
  (call/cc 
   (λ (k) (set! cont k)))
  (set! n (+ n 1))
  n)

Можно переписать в виде

(defvar *cont*)
(defun set-cont ()
  (let ((n 1))
    (setf *cont*
      (lambda ()
        (signal (make-prompt (setf n (+ n 1)) n))))))

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

ничего оно не нарушает, не обращай внимания

anonymous
()

Лол, лисперы два года (!) дрочат тему «полнофункционального call/cc», в то время как нормальные девелоперы просто разрабатывают софт.

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

в то время как нормальные девелоперы просто разрабатывают софт

Ты же тоже не разрабатываешь софт, а трындишь на ЛОРе! Так и лисперы тоже люди - им тоже иногда потрындеть охота, а не разрабатывать софт круглые сутки.

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

cl-async не использует cl-cont. Автор понял, что это тупиковая идея

Вот и я про то же. Потому что нет нативного call/cc.

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

Продолжение технически ничем не отличается от замыкания.

Undelimited отличается - тем, что оно, собственно, undelimited.

В случае undelimited вычисления из REPL оборачиваются в блок (handler-case ... (prompt (val) val)) и записываемое продолжение имеет форму (signal (make-prompt ...)).

И если у тебя там какие-нибужь eval-when'ы внутри, то как это должно будет работать?

А что будет, если ты поток из макроса запустишь?

А в чем проблема? Будет просто выполняться что-то во втором потоке. Алсо, еще интереснее - если не вызвать продолжение из макроса, а захватить во время экспанда.

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

имеет форму (signal (make-prompt ...))

если у тебя там какие-нибужь eval-when'ы внутри

Продолжение будет (signal (make-prompt ... (eval-when ...) ...)). То есть всё, кроме :execute игнорируется.

Алсо, еще интереснее - если не вызвать продолжение из макроса, а захватить во время экспанда.

Экспанд — всего лишь функция macroexpand, которая вызывается из компилятора, который вызывается из REPL. Технически, при вызове такого продолжения, будет повторён процесс компиляции с этого места.

Если же из экспанда вызвать обычное продолжение, то будет аналогично вызову из экспанда (signal ...). То есть прекратится компиляция, вызовется замыкание из продолжения и отправится сигнал prompt с результатом, который поймает REPL.

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

Продолжение будет (signal (make-prompt ... (eval-when ...) ...)). То есть всё, кроме :execute игнорируется.

А почему игнорируется, собственно?

Экспанд — всего лишь функция macroexpand, которая вызывается из компилятора, который вызывается из REPL. Технически, при вызове такого продолжения, будет повторён процесс компиляции с этого места.

А потом и исполнение, выходит?

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

Кривость в глазах смотрящего.

Так в твоих глазах cl-cont не кривой?

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

А почему игнорируется, собственно?

Потому-что «For non-top level eval-when forms, :execute specifies that the body must be executed in the run-time environment.» (c) CLHS.

Также, как и во внутренностях замыкания.

А потом и исполнение, выходит?

Если компилятор был запущен из (load ...), а не (compile-file ...), то да.

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