LINUX.ORG.RU

Promises в Scheme и других ЯП

 , ,


0

4

Добрый день!

Пишу сейчас на коленке имплементацию Scheme (r5rs) на Java (да, полностью r5rs на джаве не получится реализовать, но это и не планируется).
Немного запутался в delayed evaluation'ах и в promises.

Если открыть, например:
http://community.schemewiki.org/?delayed-computation

то там пишут:


A promise is, quite simply, the promise to evaluate an expression later.
It is not evaluated until explicitly requested, although R5RS permits promises to be evaluated implicitly when passed to primitive operations and their result be used instead.


Тоже самое пишут и на:
https://docs.racket-lang.org/reference/Delayed_Evaluation.html


A promise encapsulates an expression to be evaluated on demand via force.
After a promise has been forced, every later force of the promise produces the same result.



Т.е. promise - это просто выражение, которое мы вычислим когда-нибудь потом, либо явно через force, либо неявно.
Результат запоминается после вычисления и повтороное вычисление promise'а всегда возвращает сохраненный результат.

API для promise'в довольно простой: delay, да force.
И разные вспомогательные promise?, promise-forced?, promise-running?

Если же посмотреть promises в JavaScript или Java:
https://www.promisejs.org/implementing
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFut...

То, как я понимаю, речь немного о другом.
Future - read-only 'контейнер' для результата будущих вычислений
Promise - writable 'контейнер' для результата будущих вычислений

Т.е. допустим, мы создаем Promise и передаем его куда-нибудь, обещая положить туда результат неких вычислений когда-то в будущем.
На той стороне (обычно, в другом треде) получают созданный нами promise и в нужный момент пытаются получить его результат (блокирующий вызов).
API намного сложнее.

Как я это вижу:
в Java/JS/... - это concurrency примитив, 'контейнер' для результатов (часть методов которого - блокирующие)
в Scheme promise - это control feature, которая просто позволяет делать delayed evaluation

Так ли это?

Второй вопрос:
Будет ли в Scheme какая-то принципиальная разница (кроме memoization) между promise и обычным s-expr?

(force (delay (+ 1 2))) ; => 3
(eval  (quote (+ 1 2))) ; => 3

Т.е. должен ли я использовать Java'вский CompletableFuture (aka Promise) для реализации Scheme Promise
или достаточно будет создать обычный класс Promise, который просто хранит тело выражения (s-exp) и результат (если есть), а force просто берет тело и выполняет его (передает хранящийся в promise s-exp evaluator'у)?

★★★★★

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

Мой ридер твою высокую мысль не распарсил

не удивительно

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

добавить поле к объекту функции и доступ через что-то типа function-source.

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

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

Все JIT компиляторы являются интерпретаторами по-сути дела, что лисповский, что джаваскриптовский.

Нет, совсем не так :-) Ты далёк от темы :-) В том же SBCL есть как интерпретатор, так и компилятор :-) До недавнего времени, там был только компилятор, компилятор в нативный код, а не в говно тормознутое :-) В JS только с появлением V8 сделали компиляцию в нативный код :-)

То во что компилируется код, это вопрос десятый. JS сейчас может компилироваться в нейтив. Но это уход от темы.

Ну то для тебя вопрос десятый :-) А для меня вопрос десятый то, что для тебя вопрос десятый, т.к. твой локалхост и то, что ты там на нём запускаешь, меня мало интересует :-) Лол :-)

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

Это, в общем то, была основная фича лиспа(наряду с динамическим связыванием), до того как пошли все эти псевдолиспы. Это важно, потому что это основа рефлексии, любой код без предварительной подготовки доступен в рантайме (макросы при таком раскладе были вообще не нужны). Для ООП это не так критично, там и так есть рефлексия

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

это неправильные лисперы, они еще, я слышал, носят стринги, и красят губы. А олдскульные лисперы именно это и ценили.

Ну ка, давай, приведи сюда список «олдскульных лисперов» и то, что именно они ценили :-)

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

начни с этого вот

http://picolisp.com/wiki/?home

это олдскульный лисп

А нахрена мне с этого начинать, если мне надо сервис, который по HTTP отрабатывает хотя бы столько же, сколько Node.js? :-)

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

Это важно, потому что это основа рефлексии

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

макросы при таком раскладе были вообще не нужны

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

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

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

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

А им невдомёк, что удар - это глагол, означающий действие, при котором происходит перераспределение кинетическое энергии, может быть применителен и к существительному барабан, и к существительному палочка :-) Ведь можно ударить и палочку, и барабан :-) Поэтому фраза «ударить палочкой барабан» вызывает у некоторых адептов языков

а вообще, палочка.ударить(барабан) или барабан.ударить(палочка) даже ближе к естественному языку, чем (ударить барабан палочка), мы обычно и говорим как-то так. Кроме того, тут сразу выражена пассивная и активная сторона процесса. Это еще одна проблема лиспа, очередная, в выражении (столкновение мотоцикл грузовик), видно что произошло столкновение, но бедняжке приходится гадать, кто же с кем столкнулся на самом деле, из кода этого не видно. Это к вопросу выразительностию

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

Io, кстати, тоже код-walking, и там тоже это используется иногда. А ты про какой язык говоришь?

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

Ага, понял. Так и думал.
Спасибо за ответ!

Правильно ли я понимаю, что у них promise - это по сути лямбда, которая кеширует результат?

PS: ну и срач развели :)

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

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

так ведь если у тебя есть доступ к коду и лень, ты и без макросов любые DSL реализуешь

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

это по сути лямбда, которая кеширует результат?

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

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

Поэтому я и не говорю просто 'Лямбда', а говорю 'Лямбда, которая кеширует результат'

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

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

Вот как раз по теме топика


GovnoPromise := Object clone do (
   force := method(
     self do (if(hasSlot("cached") not, cached := doMessage(expr)))
     cached
   )
)

delay := method(
   govno := GovnoPromise clone
   govno expr := call argAt(0) 
   govno
)
force := method(GovnoPromise, GovnoPromise force )

govno := delay(1 + 1)

govno println
force(govno) println
govno println



#>>>>  GovnoPromise_0xbb5d70:
#>>>>   expr             = 1 +(1)
#>>>> 
#>>>> 2
#>>>>  GovnoPromise_0xbb5d70:
#>>>>   cached           = 2
#>>>>   expr             = 1 +(1)


безо всяких макросов.

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

а вообще, палочка.ударить(барабан) или барабан.ударить(палочка) даже ближе к естественному языку, чем (ударить барабан палочка), мы обычно и говорим как-то так.

«Ударить барабан палочка» не по-русски :-) Так же, как и «палочка ударить барабан», и так же как и «барабан ударить палочка» :-) Это обычно вы говорите как-то так :-) По-русски же будет: «ударить палочкой барабан», либо «ударить барабан палочкой» :-) И сразу тому, кто умеет читать по-русски, абсолютно понятно что и как, где «пассивная», как ты выразился, сторона, а где «активная» :-)

Это еще одна проблема лиспа, очередная, в выражении (столкновение мотоцикл грузовик), видно что произошло столкновение, но бедняжке приходится гадать, кто же с кем столкнулся на самом деле, из кода этого не видно.

Опять ты не по-русски пишешь :-) По-русски будет так: «столкнулись мотоцикл и грузовик» :-) Выражение говорит само за себя :-) А бедняжкам невдомёк, что выражение «столкнулись мотоцикл и грузовик» означает *свершившийся факт*, который в коде выразить просто нельзя, ибо свершившийся факт - это данные :-) Такие вот у нас ООП программисты, которые не могут отличит факты от действий :-) Лол :-)

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

А бедняжкам невдомёк, что выражение «столкнулись мотоцикл и грузовик» означает *свершившийся факт*, который в коде выразить просто нельзя, ибо свершившийся факт - это данные :-)

Точнее, факты то выразить в коде можно :-) И это будет как-то так: «записать факт, что „столкнулись мотоцикл и грузовик. Место - <место>, время - <время>...“» :-) Т.е. у ООП адептов должен нарисоваться образ класса РегистраторФактов с методом «записать факт», у проектировщиков БД - сущности «Мотоцикл», «Грузовик» и отношение «многие-ко-многим» «Столкновение» и т.д. :-)

anonymous
()

Тред не читал, но кто-то уже спрашивал, почему

полностью r5rs на джаве не получится реализовать

?

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

Ну и что? Другие jvm-языки же как-то справляются. Можно при компиляции такую рекурсию в цикл, например, преобразовывать, было бы желание.

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

Другие jvm-языки же как-то справляются.

Какие например? Frege?

Ни scala, ни clojure точно не справляются. Но это так, на заметку, чтобы ты знал ;)

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

Скала как раз справляется именно так, как я написал. К сожалению, не в общем случае, но работает. Даже если компилятор сам не угадает, что метод можно оптимизировать, можно поставить @tailrec и он попытается объяснить, почему нет.

Но если я правильно понял то, что написано здесь: https://groups.csail.mit.edu/mac/ftpdir/scheme-reports/r5rs-html.old/r5rs_22...., то как раз для подобных, самых строгих случаев, поддержка требуется, а для более общих - допускается.

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

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

Косвенная рекурсия. Проблема в том, что scala не умеет оптимизировать TCO для косвенной рекурсии. Это когда хвостовой рекурсивный вызов не вызывается напрямую, а передается в аргументе вниз по рекурсии через лямбду. Все вызовы остаются при этом хвостовыми. Это критически важно для продолжений.

Из-за этого неумения, кстати, плагин продолжений отжирает всю память, если написать «while(true) {}», а не должен был бы, если бы была настоящая оптимизация TCO.

Блин, я столько раз писал здесь о том, что в scala нет полноценного TCO, но я понимаю, что ты не всегда меня читаешь) Хотя я здесь часто вижу, что большая часть местного народа ни фига не понимает TCO и наивно полагает, что @tailrec решит все их проблемы.

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

Для интереса можешь погуглить, как Одерский с компанией собирались реализовать так называемый «трамплин», но потом почему-то передумали, видимо из-за плохих скоростных характеристик полученной реализации TCO. Следы этого трамплины были замечены в scaladoc, хотя, может быть, их уже удалили.

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

- TCO
- first-class continuations можно, но сложно реализовать (http://www.ccs.neu.edu/racket/pubs/stackhack4.html). И насколько я понимаю, можно реализовать только Upward continuation
- система типов в Java слабовата (type erasure тоже жизнь не упрощает), сложно, например, реализовать full numeric tower

Самая полная реализация Scheme на Java которую я знаю - Kawa.
И даже у них есть пара оговорок:
http://www.gnu.org/software/kawa/Compatibility.html

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

ты видать не понял. Я говорю о выражении активной и пассивной стороны. Из read(john, book) не видно кто кого читает, ваня книгу или книга ваню

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

то есть, ты считаешь, что если в языке реализации нет каких то фич, то и в реализуемом языке их быть не может? Интересно, а в асме есть ТСО. Как ты с такими понятиями взялся за это дело, вообще непонятно. А call/cc как будешь пилить? В жабе его нет, все пропало.

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

Это критически важно для продолжений.

в продолжениях (схемовских, полноценных) вообще ТСО в общем случае невозможно. Это ты о чем то о своем.

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

если в языке реализации нет каких то фич, то и в реализуемом языке их быть не может?

Рантайм то в случае скалы и джавы и кложуры общий.

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

Потому что ты не можешь обойти ограничения host VM (JVM в данном случае).

Rich Hickey:

No language that uses the JVM stack and calling convention (e.g. Clojure and Scala) can do TCO since it would require either stack manipulation capabilities not offered in the bytecode spec or direct support in the JVM. The latter is the best hope for functional languages on the JVM, but I'm not sure is a priority for Sun as tail-calls are not idiomatic for JRuby/Jython/ Groovy.

Еще:

Native instruction sets often let you do whatever you want with the stack. C doesn't in the ANSI standard, but you can do it with a bit of assembly. .NET IL has an explicit instruction for tail calls. The JVM, on the other hand, is very strict about how you use its stack and has no tail call instruction.

Да, ты можешь отказаться от Java calling convention и не использовать JVM stack, но тогда теряешь interop и performance.

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

Я может чего то не понимаю, давай возьмем к примеру ТСО. В контексте реализации это означает, что твой интерпретатор должен распознать хвосторекурсивный вызов и развернуть его в цикл. Причем тут низлежащая ВМ?

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

Читай выше.

Например, раньше в JVM было такое:

in jdk classes [...] there are a number of security sensitive methods that rely on counting stack frames between jdk library code and calling code to figure out who's calling them.

Потом это, вроде как, поправили, но остались другие ограничения + Java calling convention и т.п.

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

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

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

то есть, то что ты утверждаешь, равноценно тому, что утверждать, что жаба не умеет циклы

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

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

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

После того, что ты сказал о Хики, это звучит похвалой.

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

ты видать не понял. Я говорю о выражении активной и пассивной стороны. Из read(john, book) не видно кто кого читает, ваня книгу или книга ваню

Ну, видимо, тебе не привыкать, что книга может читать Джона :-) Сам то книги читаешь какие-нибудь? :-) Ещё кого то подозреваешь в непонимании :-) Лол :-)

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

Я уже в курсе, что среди множества всех разновидностей продолжений ты выбрал какое-то одно, и вечно талдычишь здесь об этом)

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

А ты уверен, что performance теряется? В книге PAIP описан calling convention, который автоматом обеспечивает TCO. Ты думаешь, что там плохой perfomance?

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

Не могу сказать точно, нужно проверять.
Но думаю, что если забить на Java calling convention и вместо него пытаться сделать свой, то это будет крайне сложно сделать, и да, скорее всего performance упадет (хотя бы потому, что это будешь делать уже не на C).

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

Ты мог бы расширять не только целевой язык, но и язык макров(то есть, в твоем случае define-syntax — вот это вот все).

Так ты можешь, кто тебе мешает? define-syyntax - обычная функция, бери да переопределяй.

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

теперь покажи как оно с замыканиями работает типа:

let govno;
let x = 1;
{
    let x = 2;
    govno = delay(x+1)
}
print force(govno) ;выводит 3, а не 2

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

так ведь если у тебя есть доступ к коду и лень, ты и без макросов любые DSL реализуешь

Конечно реализуешь. И даже без доступа к коду и лени реализуешь! Вопрос только в затратах. С макросами - затраты минимальны.

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

. Можно при компиляции такую рекурсию в цикл

ТСО никак не связана с рекурсией. Она оптимизирует ВСЕ хвостовые вызовы, абсолютно. Не важно, рекурсивные или нет.

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

Как раз возможно, более того - оно обязательно.

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