LINUX.ORG.RU

Откуда у Scheme такие макросы?

 ,


1

2

Существует ли книга, в которой написано, почему макросы в scheme такие странные.

Логика макросов CL понятна. Всё есть список, дальше программист сам разберётся.

Часть решений в Scheme тоже: гигиена по-умолчанию, паттерны.

Но зачем нужна отдельная от списка структура кода (syntax)? Почему этот syntax, будучи созданным внутри макроса не привязывается автоматически к строке, где был использован макрос (вместо #' #` приходится писать syntax/loc)?

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

Объясни, пожалуйста, в чем подвох:

#lang racket/base

(require (for-syntax syntax/parse racket/base))

(define-syntax (m stx)
  (syntax-parse stx
    [(_ (~literal else1))
     (syntax 'ok)]))
> (m else1)
'ok
> (define else1 #f)
> (m else1)
'ok
> (let ([else1 #f]) (m else1))
m: expected the identifier `else1' in: else1

Если сделать так, то тоже ошибка:

#lang racket/base

(require (for-syntax syntax/parse racket/base))

(define-syntax (m stx)
  (syntax-parse stx
    [(_ (~literal else1))
     (syntax 'ok)]))

(module+ test
  (define else1 #f)
  (m else1))

m: expected the identifier `else1' in: else1

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

Идентификаторы которые не связаны ничем не отличаются от идентификаторов которые связаны в топ-левел (например если в макростеппере нажать на несвязанный модификатор то там в свойствах так и пишет unbound _or toplevel_). В твоем случа else1 в определении макроса unbound, при определении в топлевеле он и остается unbound, все матчится, а вот когда ты его определил в модуле или локальном конексте - это уже получается else1 с другим связыванием и не матчится

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

(let ([else #f]) (cond [else 1])) должно быть #f а у тебя 1

А в стандарте написано «if there is an else clause its expressions are evaluated and the results of the last are the results of the case expression». Так что должно быть 1. Если считать, что [else 1] в (cond [else 1])) — «else clause».

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

Можно пример? Пока, с моей точки зрения, ломается стандартный код, так как я (возможно испорченный CL с loop-macro) не ожидаю, что синтаксическое значение символа внезапно зависит от его значения. В CL (let ((in 1) (for 2)) (loop for i in '(1 2 3) collect i)) работает.

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

В CL (let ((in 1) (for 2)) (loop for i in '(1 2 3) collect i)) работает.

Lopp не показатель, в нем есть некоторе костылестроение что бы это работало.

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

Если считать, что [else 1] в (cond [else 1])) —«else clause».

Но это не else clause :)

В CL (let ((in 1) (for 2)) (loop for i in '(1 2 3) collect i)) работает.

Некорректное сравнение так как в лупе на месте for и in в данном сдучае ничего кроме кейвордов быть не может. В случае с else там может стоять как кейворд так и произаодьная форма/идентификатор.

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

Можно пример?

Ваш макрос (>>= f x) раскрывается в (cond [x (f x)]), спецификация: если х не #f то применить к х f, иначе вернуть #f (монада мейби такая из твоего предыдущего треда :)). Я пишу следующее: (let ([else 1]) (else . >>= . add1)), и вместо результата получаю говно. классический пример протекаюзей абстракции - имя переменной не может быть else изза особенностей реализации, которых пользователь знать не должен. Он вообще ничего не доден знать о том во что макрос раскроется.

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

else = #f в предыдущем примере конечно, а не 1

anonymous
()
Ответ на: комментарий от anonymous
(define-syntax (m stx)
  (syntax-parse stx #:literals (else1)
    [(_ else1)
     (syntax 'ok)]))

Что характерно, если сделать так, то уже ругается:

syntax-parse: literal is unbound in phase 0 (phase 0 relative to the enclosing module) in: else1
Это так задумано? В документации не нашел упоминания, по идее должно быть одно и то же.

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

классический пример протекаюзей абстракции - имя переменной не может быть else изза особенностей реализации

Ясно. В CL проблемы нет из-за отдельных пакетов и договрённости не переопределять в чужих пакетах. А если переопределять, то осознанно (зато удобно делать monkey patching).

А вообще символ в Cl и Scheme какие-то сильно разные сущности. В CL = обозначение для ячейки с двумя слотами, точнее стека таких ячеек. В Scheme каждый символ тащит за собой набор значений в разных окружениях, точнее по набору для каждой фазы. Хотя привыкнуть можно.

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

Ясно. В CL проблемы нет

В CL она есть и никак не решается. Пакеты вообще ортогональны - они на ситуацию никак не влияют

договрённости не переопределять в чужих пакетах.

А не может быть никакой договоренности. Тот кто использует macro вообще не в курсе о том, что где-то в зависимостях есть cond с else. А раз он об else не знает - то и договориться о неиспользовании нельзя. Кроме того - это елсе может появиться при использовании кода третьей стороной, при чем заранее вы никак не скажете будет оно ьам или нет.

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

То естб в CL вам надо каждый раз при написании макроса думать - а может ли тут произойти косяк с кейвордами или нет, а как часто он может произойти, как изменитб реализацию, чтобы это было как можно реже и т. п. тут же просто сравниваешь идентификаторы literaly вместо eq?, и все just works. В этом и сутл схемовской макросистемы - позволить макрописателю не тратить время на реализацию руками того, что и так делается искаробки, да еще и лучше

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

а может ли тут произойти косяк с кейвордами

Если это именно keyword, то он либо принадлежит пакету keyword (кстати в Racket тоже такие есть — #:keyword вроде в let запихать нельзя) либо принадлежит тому же пакету, что и макрос (iter:iter (iter:for i from 1 to 10) (iter:collect i)). Вызовы функций в экспанде макроса могут быть либо из пакета макроса, либо библиотеные. А если пользователь переопределяет (пусть локально) функции из чужих (библиотечных) пакетов, то он обычно делает это намеренно.

В этом и сутл схемовской макросистемы - позволить макрописателю не тратить время на реализацию руками того, что и так делается искаробки, да еще и лучше

Это да. Единообразия больше. Следствие из идеи «гигиены».

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

А если, допустим, в cond заменить => на #:=>, а else на #:else, что нибудь изменится? Ключи нельзя переопределить ведь?

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

А если, допустим, в cond заменить => на #:=>, а else на #:else, что нибудь изменится?

Тогда у ~literal не будет недостатков. Но это уже не Scheme, а Racket.

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

У кейвордов вроде связывания нет, они интерпретируются по той же схеме что например числа или #t/#f.

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

Так речь не о макросах/функциях а о кейвордах вроде for или to.

(iter:iter (iter:for i from 1 to 10) (iter:collect i))

iter:for — вполне себе кейворд в том смысле, что вне формы iter он смысла не имеет (существует версия iterate с синтаксисом (iter:iter (:for ...) ...)). Но он принадлежит пакету iter. Символы from и to берутся по написанию (iter:for <var> from <int1> to <int2>). Переменной в том месте быть не может.

Соответственно

(labels ((for (x) (cons "my-for" x)))
  (iter:iter (iter:for i from 1 to 10) (iter:collect i)) 

продолжает работать, так как iter:for и for — разные символы.

С другой стороны можно написать (defmacro iter:for ...) и переопределить всюду поведение iter:for на «более правильное», если очень хочется. Хотя с точки зрения Scheme, я так понимаю, это недостаток, а не достоинство.

«Проблемой» является бездумное употребление use-package, а потом случайное переопределение импортированных символов, поэтому Google, например, настойчиво рекомендует использовать квалифицированные имена символов, а не импортировать их в текущий пакет.

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

будет профит или нет

Для новых макросов, которые предполагается использовать только в Racket — разумеется, да. Но непортируемо в рамках Scheme.

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

Вот только так не работает:

(define-syntax (m stx)
  (syntax-parse stx
    [(_ (~literal #:else1))
     (syntax 'ok)]))

syntax-parse: expected identifier at: #:else1 in: (syntax-parse stx ((_ (~literal #:else1)) (syntax (quote ok))))
monk ★★★★★
() автор топика
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от qaqa

Не совсем понял вопрос. Профит от того, чтобы везде делать кейворды #:, а не через literal связывание? Мне лично второй вариант нравится больше, как-то больше дает свободы.

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

iter:for

тьфу ты. from или to. Пишу с телефона, неудобно проверять посты.

Переменной в том месте быть не может.

Я уже об этом говорил. По-этому здесь случай проще и можно свободно сравнивать по eq? (паттерн (~datum ...)). Но тут именно суть в том, что лучше не тратить на это дело время и не задумываясь ставить ~literal, тогда все будет хорошо. А вот уже потом, если будет желание, можно подумать и поменять на ~datum или сделать #:-кейвордом.

В случае elst же у нас на том месте и переменная может стоять спокойно.

продолжает работать, так как iter:for и for — разные символы.

Так тут речь именно о несвязанных кейвордах. loop тут плохой пример.

Хотя с точки зрения Scheme, я так понимаю, это недостаток, а не достоинство.

Да и в схеме можно переопределить любой идентификатор

поэтому Google, например, настойчиво рекомендует использовать квалифицированные имена символов, а не импортировать их в текущий пакет.

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

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

но никто так не делает из-за вербозности

По всякому. xml или какой-нибудь opengl почти всегда через квалификатор. А вот упомянутый iter почти всегда импортируют в пакет, так как вопринимается не как вызов библиотеки/функции, а как расширение языка.

не задумываясь ставить ~literal, тогда все будет хорошо

Кстати в связи с макросами вопрос: как-нибудь можно отследить, что при изменении макроса всё, что от него зависит, перекомпилировалось? Или просто верить, что require всё разрулит?

И про require. https://github.com/Kalimehtar/Racket-gir Кода всего ничего. Всюду #lang racket/base . Зависимость от библиотек минимальная. Но при (require «test.rkt») тупит на 5-10 секунд. Как увидеть, что он при этом делает?

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

Ну так второй вариант тоже не идеальный. Был в рассылке тред: жаловалcя кто-то, что на автомате делает в match последний шаблон не _, а else, и ему это else у вложенного cond переопределяет, но он не сразу понимает, почему код не то делает, что нужно.

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

Кстати в связи с макросами вопрос: как-нибудь можно отследить, что при изменении макроса всё, что от него зависит, перекомпилировалось?

Э. в каком смысле? Если run в Dr.Racket делать то там по хешам проверяется, и если какойто файл из зависимостей был изменен, то он перекомпилируется. Это гарантируется.

Но при (require «test.rkt») тупит на 5-10 секунд. Как увидеть, что он при этом делает?

импортирует кучу всего. Скорее всего gui, в том числе. Чтобы узнать импортируемые модули - можно запустить module browser.

А вот упомянутый iter почти всегда импортируют в пакет, так как вопринимается не как вызов библиотеки/функции, а как расширение языка.

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

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

Ну а почему он _не должен_ переопределят? Как раз переопределение - это поведение в соответствии со спецификацией.

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

Про спецификацию никто не спорит. Но пока ощущение, что вещи вроде else и => лучше делать как непереопределяемые #:else #:=>. А какие минусы такого решения? В чем проявятся?

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

если какойто файл из зависимостей был изменен, то он перекомпилируется. Это гарантируется.

(require ...) в racket в командной строке — также проверяет?

вполне может какой-то идентификатор из расширения зарезервировать для своих нужд

Чтобы использовать «идентификатор из расширения», надо это расширение явно указать в (use-package ...). Значит можно считать, что пользователь осознанно переопределяет идентификатор из расширения (а последствия будут не только в макросах, но и во всех функциях, что используют это расширение).

импортирует кучу всего. Скорее всего gui

Да нету там gui. В одном файле обнаружил lang typed/racket/base. Сменил на racket/base — стало 2 секунды. Получается, он всё по ссылкам грузит всегда, а не только при компиляции?

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

(require ...) в racket в командной строке — также проверяет?

так require запрашивает rkt-файл (исходник тоесть) и только если в соответствующей папке будет лежать соответствующий файл с нужным хешем - он его _не_ перекомпилирует. А стандартное поведение - перекомпилировать.

Получается, он всё по ссылкам грузит всегда, а не только при компиляции?

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

Чтобы использовать «идентификатор из расширения», надо это расширение явно указать в (use-package ...). Значит можно считать, что пользователь осознанно переопределяет идентификатор из расширения (а последствия будут не только в макросах, но и во всех функциях, что используют это расширение).

так где определены from/in?

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

Вообще есть некие определенные негласные правила, когда ставить кейворд с #:, а когда без. Вот кейворды loop'a надо делать с #:, а кейворды iter без :)

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

так где определены from/in?

Нигде. От них используются только имена. С таким же успехом можно было бы писать «from»/«in». Или :from/:in.

А если символ имеет значение или функцию, то он обязательно имеет хозяина. Напрмер, it внутри aif или collect внутри iter. Или в аналогичной cl-форме (case i (a 1) (otherwise 2)) символ otherwise принадлежит пакету COMMON-LISP, также как и символ case. Хотя пример опять некорректный, так как значение здесь не используется...

Фактически везде, где потенциально могло бы использоваться значение, но есть особые значения. Исключением можно считать символы &rest &key &optional в lambda-параметрах. Это обычные символы пакета COMMON-LISP. Если кто-нибудь захочет их использовать как переменные, то могут быть нюансы:

>(let ((&key 1)) &key)
1

> (destructuring-bind (a b) (list 1 2) (cons a b))
(1 . 2)

> (destructuring-bind (&key &rest) (list 1 2) (list &key &rest))

** - Ошибка в возможностью продолжения
Лямбда-список макроса DESTRUCTURING-BIND содержит неудачно размещенное &REST.

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

Вот кейворды loop'a надо делать с #:

Неправда. Вот цитата из стандарта:

http://www.lispworks.com/documentation/HyperSpec/Body/06_ah.htm

Просто реализация позволяет делать кейворды из любого пакета, в том числе и keyword :-)

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

А если несколько макросов используют один и тот же кейворд? У literal еще тот плбс что можно сделать (rename-in [else new-else]) и везде вместо else использовать собственно new-else. Ни и гигиена еще позабоьиься чтобы при экспанде кейворды из разных модулей не пересекалиь

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

#%app это не кейворд а обычный идентификатор, как + или if например

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

А если несколько макросов используют один и тот же кейворд?

Если это именно :keyword, то разницы нет. Если символ, то значит макросы в одном пакете и у них один автор. Ну или это «кейворды» типа from/to, тогда всем также пофиг.

У literal еще тот плбс что можно сделать (rename-in [else new-else])

Вот это реально круто. Ради аналога prefix-in я в CL свой reader писал: https://github.com/Kalimehtar/advanced-readtable Правда выгляделу у меня это так:

(set-macro-symbol '|NEW-ELSE|
  (lambda (stream symbol)
     (declare (ignore stream symbol))
        'package-with-else:else))

А тут всё просто, красиво и работает.

гигиена еще позабоьиься чтобы при экспанде кейворды из разных модулей не пересекалиь

Этого не понял.

#lang racket
(require racket/contract ffi/unsafe)

. module: identifier already imported from a different source in:
  ->
  ffi/unsafe
  racket

Приходится писать (rename-in racket/contract (-> -->)). Нельзя ли как-нибудь указать, что биндинг в такой конструкции оттуда, а в эдакой — отсюда. А то на каждый контракто-подобный модуль новых стрелочек не напасёшься, а стрелочка с префиксом выглядит издевательски. В sbcl для такого делали конструкцию iter::(iter (for ....)) то есть «в этих скобках текущий модуль вот такой».

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

я говорил о макросах с соответствующим синтаксисом

Понял. В CL тоже в общем случае не принято ставить :keyword головной формой. Хотя бывают исключения, если может быть неоднозначность «кейворд или переменная».

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

Подчеркивание много где используется просто. По идее его надо переделать через syntax-parameterize, но, видимо, забили. Один из минусов динамично развивающегося языка :)

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

Если это именно :keyword, то разницы нет. Если символ, то значит макросы в одном пакете и у них один автор. Ну или это «кейворды» типа from/to, тогда всем также пофиг.

А если макросы не в одном пакете и это не кейворды типа from/to? :)

Этого не понял.

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

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

. В sbcl для такого делали конструкцию iter::(iter (for ....)) то есть «в этих скобках текущий модуль вот такой».

По идее можно аналогичную конструкцию сделать но это будет весьма хитро. Для начала надо определиться с конкнетной спецификацтей того как это форма будет рабоьать. Тут это самле сложное.

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

если макросы не в одном пакете и это не кейворды типа from/to

Тогда каждый макрос будет использовать кейворд из своего пакета. Например, есть iter:collect и есть dynamic-collect:collect.

Выглядит так

(let* (odd-vals
        (even-vals
          (dynamic-collect:with-dynamic-collection ()
            (setf odd-vals 
              (iter:iter (iter:for i from 0 to 100)
                (if (evenp i) 
                    (dynamic-collect:collect i)
                    (iter:collect i))))))))

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

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