> Журнализм плюс реклама левого сайта.
1. Правильный заголовок: принят новый стандарт scheme - r6rs
2. Правильная ссылка: http://r6rs.org/
А так новость хорошая. :)
Товарищи модераторы! Просьба прислушаться к комментариям учаснега Teak.
>Furthermore, if you are in the pragmatist camp, the proposed R6RS does not
go far enough. Pragmatists want things like complete, pervasive object
systems; built-in support for concurrency; and beefy standard libraries
including most common OS and networking functions, having been spoilt on
such features from exposure to the likes of Common Lisp, Perl, Python, C#
and Java. As a pragmatic standard the proposed R6RS doesn't go nearly far
enough; its benefit is mainly in political issues, as a "step in the right
direction" for the Scheme community. Many of the problems that the proposed
report addresses have been addressed better by SRFIs to R5RS, with which
the report conflicts in subtle ways.
Полностью согласен. Без библиотек даже самый мощный язык никому не нужен
Я вообще расстроен новым стандартом. Это уже не схема, это какой-то недо-cl :(
Стандарт распух до 160 страниц против бывших сорока.
Включили совершенно ненужный синтаксис.
Вот так теперь предлагается возвращать несколько значений из функциии:
(define return-many
(lambda ()
(let ((a (read))
(b (read)))
(values a b))))
И вот так принимать:
(call-with-values
(lambda () (return-many))
(lambda (x y)
(+ x y)))
Можно было раньше обойтись без этого? Разумеется:
;; lisp style
(define return-many
(lambda ()
(let ((a (read))
(b (read)))
(list a b))))
(apply + (return-many))
;; scheme style
(define return-many
(lambda ()
(let ((a (read))
(b (read)))
(lambda (f) (f a b)))))
((return-many) (lambda (x y) (+ x y)))
Зачем придумали (и включили в стандарт) велосипед? Ответ мутный:
типа, так нам будет проще реализовывать и оптимизировать компиляторы.
>Можно было раньше обойтись без этого? Разумеется:
Осознай разницу между (values a b c d ...) и (lisb a b c d...). Тут разная реализация, и в случае с (values..) она может быть эффективнее, так как чтобы добраться до десятого элемента списка , надо пропахать весь список, а в случае с (values...) значения укладываются в стек и могут адресоваться напрямую.
Цитата с http://www.r6rs.org/ratification/results.html :
"Name: Arjun Guha
Location: Providence, RI, USA
Affiliation: Brown University
Ratify: YES
I am an idiot for not replacing this
text."
:):):):):)
Если values сделаны как в CL, то есть еще одно преимущество — если "принимающей стороне" не нужны дополнительные значения, то она может их просто игнорировать. Новые схемовские values будут работать так же?
А кто запретит? Это даже как-то оговаривать стандартом странно было бы. Думаю, что это поведение списывается на конкретную имплементацию. Я думаю, что из CL это и утащили.
>Вот так теперь предлагается возвращать несколько значений из функциии:
Поправьте меня, если не прав, но values и call-with-values были и в прошлой редакции стандарта (по крайней мере это работало во всех реализациях, с которыми я встречался).
> Осознай разницу между (values a b c d ...) и (lisb a b c d...). Тут разная реализация, и в случае с (values..) она может быть эффективнее
Чем это кошернее, чем вернуть замыкание, как я привел в примере? Зачем мне, как пользователю _лиспа_, просчитывать в башке варианты с регистрами, стеками и прочей x86-ересью? :) Пусть компилятор за меня эти моменты оптимизируют (тем более, конкретно вышеприведенный пример просчитать нетрудно). Если мне это надо залезть в кишки машины, я лучше возьмусь за Си, правильно? А так имеем одну лишнюю конструкцию (длинную причем), не несущую смысловой нагрузки.
> Поправьте меня, если не прав, но values и call-with-values были и в прошлой редакции стандарта (по крайней мере это работало во всех реализациях, с которыми я встречался).
Ну он действительно много где работает, но в r5rs его нет :)
ага.. судя по всему ребята решили сделать коммерчески успешный продукт, пригодный для универсального программинга. это зря конечно, максимум что из схемы на этом пути получится -- еще один забытый и выброшенный на свалку истории безвестный язык. в общем, R4RS рулит, а остальное втопку.
>Чем это кошернее, чем вернуть замыкание, как я привел в примере?
Нет адресации к элементам списка. А если мне возвращаемое значение не
нужно в виде списка, у элементов которого одинаковая смысловая
нагрузка и они подвергаются одной операции (у тебя -- сложение)? Если
мне разные по смыслу возвращаемые объекты нужны? А если мне не все
значения нужны, а десятое (и такие примеры у меня есть практические,
но в CL). И компилятор ничего оптимизировать не сможет из-за природы
списка, так как он не знает наперед, тебе список нужен или набор
независимых значений, пока ты ему специальным образом не укажешь на
семантику твоего списка. Вот и сделали (values...).
Придание особой семантики набору
возвращаемых значений из функции не противоречит духу Scheme. Список
возвращать никто тебе не запретит. Если твоя программа, еще раз
повторю, использует возвращаемое значение, как список (например (apply
'+ ...)), то и возвращай список.
А CL, например, если используется values, это дело понимает и пытается
оптимизировать. Вот тупой пример из SBCL без всяких оптимизаций:
* (defun foo () (values 1 2 3 4 5 6))
FOO
* (disassemble 'foo)
; 0A7D0692: BA04000000 MOV EDX, 4 ; no-arg-parsing entry point
; 97: BF08000000 MOV EDI, 8
; 9C: BE0C000000 MOV ESI, 12
; A1: C745F010000000 MOV DWORD PTR [EBP-16], 16
; A8: C745EC14000000 MOV DWORD PTR [EBP-20], 20
; AF: C745E818000000 MOV DWORD PTR [EBP-24], 24
; B6: 8BDD MOV EBX, EBP
; B8: B918000000 MOV ECX, 24
; BD: 8B6DFC MOV EBP, [EBP-4]
; C0: 8D63E8 LEA ESP, [EBX-24]
; C3: F9 STC
; C4: FF63F8 JMP DWORD PTR [EBX-8]
; C7: 90 NOP
; C8: CC0A BREAK 10 ; error trap
; CA: 02 BYTE #X02
; CB: 18 BYTE #X18 ; INVALID-ARG-COUN
T-ERROR
; CC: 4D BYTE #X4D ; ECX
; CD: CC0A BREAK 10 ; error trap
; CF: 02 BYTE #X02
; D0: 18 BYTE #X18 ; INVALID-ARG-COUN
T-ERROR
; D1: 4D BYTE #X4D ; ECX
;
NIL
*
Ну там используется внутренее прдставление этих 1..6, но это не важно.
Как видишь, первые три значения упихиваются в регистры, а остальные
три располагаются в памяти. Если бы регистров было больше, то значения
раскидались бы максимально в регистры.
> И компилятор ничего оптимизировать не сможет из-за природы
списка,
> так как он не знает наперед, тебе список нужен или набор
независимых значений,
> пока ты ему специальным образом не укажешь на
семантику твоего списка.
> Вот и сделали (values...).
Слушай, я это все понимаю :)
Я сам в свое время писал компилятор tcl-подобного языка.
Я вот о чем: у меня есть задача -- вернуть несколько значений. Почему мне, вместо компилятора,
надо выбирать подходящую реализацию этой задачи? Возьмем r4rs-совместимый вариант:
(define return-3
(lambda () (lambda (f) (f (read) (read) (read)))))
((return-many) (lambda (a b c) (+ a c)))
Оптимизируется для компиляции элементарно: инлайнится return-3,
удостоверяется, что параметр b не используется, выкидывается, и
получаем в итоге (+ (read) (read)). Зачем тут какие-то values?
>Оптимизируется для компиляции элементарно: инлайнится return-3, удостоверяется, что параметр b не используется, выкидывается, и
получаем в итоге (+ (read) (read)). Зачем тут какие-то values?
Ха, а если функция не инлайнится (ну большая и сложная функция), вызывается она в десяти разных функциях, а возвращаемые параметры используются по-разному. Как тут быть? Инлайнить десять раз функцию во всех местах, где она вызывается? Вот пример: есть функция, возвращающая три координаты x, y и z. Она не инлайнится. И в одной вызывающей функции мне нужно только проверить координату z. Вызывается эта функция в цикле 1000 раз. Вопрос: как получить эту координату без прохода по списку (list x y z) 1000 раз.
> Ха, а если функция не инлайнится (ну большая и сложная функция), вызывается она в десяти разных функциях, а возвращаемые параметры используются по-разному. Как тут быть?
Чувствую, у тебя серьезный опыт работы с CL :) На схеме программы пишутся несколько по-другому -- обязательная оптимизация хвостовых вызовов благоприятствует функциональному подходу.
> Вот пример: есть функция, возвращающая три координаты x, y и z. Она не инлайнится. И в одной вызывающей функции мне нужно только проверить координату z. Вызывается эта функция в цикле 1000 раз. Вопрос: как получить эту координату без прохода по списку (list x y z) 1000 раз.
Конкретно в этом примере -- передать в функцию с координатами тело цикла в замыкании :)
Наши мнения расходятся вот в чем: CL -- это мощный императивный язык с развитой макросистемой и инкрементальной компиляцией, в нем идет явное разделение на код и данные. В твоем примере про точки ты мыслишь как раз этими категориями: есть код, вычисляющий точки, и есть данные -- возвращаемые точки. Программирование на схеме изначально сдвинуто в более функциональную область -- код с данными уже так четко не делят (в схеме даже имена функции с переменными делять одну зону видимости -- отсутствуют function / funcall).
Что мне конкретно не нравится: по стандарту R6RS отчетливо видно, что схема явно идет к Common Lisp'у. Дело в том, что если мне будет нужен CL, я буду программировать на CL, а не на "недо"-CL R6+RS Scheme. Это тупиковый путь развития, следует именно подчеркивать отличия языков, нежели делать их одинаковыми.
Так ведь и Scheme и Common Lisp -- это разные языки.
Scheme -- это continuations, хвостовая рекурсия, удобное функциональное программирование, академическая простота языка и компактная реализация. Я сейчас пишу код в связке scheme (chicken) + Си, они отлично друг с другом взаимодействуют, поэтому в огромной стандартной библиотеке я не нуждаюсь.
Common Lisp, прежде всего, это отличный самостоятельный язык, с богатой собственной стандартной библиотекой. Из killer-features особо отмечу лучшую в мире макро-систему, и инкрементальную компиляцию. Люди, которые с CL близко знакомы, знают, насколько это мощные инструменты.
>Наши мнения расходятся вот в чем: CL -- это мощный императивный язык с развитой макросистемой и инкрементальной компиляцией, в нем идет явное разделение на код и данные.
я бы сказал, что он *и* императивный. Никто функциональщины у CL не отнимал.
Мне пришлось подзадержаться с ответом, так как решил ознакомиться с мощными баталиями по вопросу values в comp.lang.scheme. Там мнения, как за, так и против. И вообще, там очень много всего. За короткое время мне не ознакомиться. Основное из "за", что я успел захватить -- это оптимизация вызовов (о чем, собственно я тут и речь вел) и еще кое-какие аргументы с примерами встречались в области ограждения от ошибок программиста (как я понял). Еще были предложения по поводу введения tuples, что автоматом решит для Scheme проблему values (по сути отпадет необходимость), так как будет возвращаться одно значение, но в котором будет tuple [1 2 3]. А случай с одним возвращаемым значением считать [a] = a. Ну и, соответственно, там вопросы, надо ли делать это first-class объектом или нет. Мне тяжеловато глубоко погружаться в такие длинные обсуждения там. Я понял одно: спор этот среди схемотехников уже имеет статус *религиозного*, а частью этого спора мне никак не стать -- мало конкретных знаний.
Да, но в этом стиле программировать на CL официально не рекомендуется, как минимум из-за того, что стандарт не требует обязательной tail-call optimization.
> Я понял одно: спор этот среди схемотехников уже имеет статус *религиозного*
Согласен :) В основном, канат перетягивается либо в сторону компактности, чистоты и академической красоты языка, либо в сторону универсальной практичной платформы а-ля CL. Лично я придерживаюсь первых, но, судя по прогрессу стандартов, побеждают вторые :(
Новый стандарт определяет очень нужные вещи: модули, , бинарный ввод/вывод, хеш-таблицы, структуры (наконец-то :), а также множество функций и макросов, которые были реализованы почти везде, но до сих пор не были стандартизированны.
R6RS позволит писать более переносимые библиотеки, что в свою очередь сделает язык более практичным, а реализации более-менее взаимозаменяемыми. Возможно принятый стандарт не идеален и написан не в духе традиций аскетиченой схемы, но лучше практичная реализация сейчас, чем идеальный язык никогда :)
Сам лично пользуюсь реализацией схемы на яве - SISC (http://sisc-scheme.org/). Полезная штука, из которой можно пользоваться существующими библиотеками/наработками на яве и в то же время программировать на действительно красивом и выразителном языке - на лиспе.
Мой скромный вклад: небольшая библиотека для текстовых шаблонов, при помощи которой можно генерировать динамические странички или любой другой текст: http://text-template.sourceforge.net
>Да, но в этом стиле программировать на CL официально не рекомендуется, как минимум из-за того, что стандарт не требует обязательной tail-call optimization.
Ну, скажем так, те, кто ориентируются на определенные реализации, которые это поддерживают (а это подавляющее большинство реализаций), могут использовать хвостовые вызовы без проблем. Но если говорить о переносимости между реализациями, то лучше, конечно, воздерживаться от хвостовых вызовов или использовать их при небольших объемах данных. Я нарвался один разок, когда писал патчи, чтобы CLX на clisp работал. Там был пример рисования узора (в demos::do-all-demos) с весьма глубокой рекурсией. SBCL ее оптимизировал без проблем и шустренько рисовал, а clisp вываливался с переполнением стека. Я голову ломал, а потом вот А после меня на это же наступил один француз, которому я объяснял, как поднять Garnet/CLX на CLISP под MacOS. Пришлось запрашивать при старте clisp бОльший стек. Так что я от хвостовых рекурсий действительно воздерживаюсь.