LINUX.ORG.RU

Макросы + история успеха

 , , ,


2

3

Комрады. Сабж собственно - чем макросы racket лучше/хуже макросов cl и наоборот?

Второе - где, какой диалект и для чего (лиспа) вы применяете?

Всем спасибо.

З.Ы.: интерес к этому так как начали писать с коллегами большую система на racket. Стало интересно. Делаем just for fun

Вобщем канонический пример «удобства» макросов схемы и Racket:

Надо определить макрос, который бы позволял «линейно» писать код, как в сишечке, чтобы текст не сползал вправо постоянно.

Типа чтобы

(multiple-value-bind (a1 b1 p1) (foo1)
  (with-open-file (f1 p1 ...)
    (let ((x1 (read f1)))
      (when x1
        (multiple-value-bind (a2 b2 p2) (foo2)
          (with-open-file (f2 p2 ...)
            (let ((x2 (read f2)))
              (when x2
                (bar x1 x2 ...))))))))

Превращался в

(nest
 (multiple-value-bind (a1 b1 p1) (foo1))
 (with-open-file (f1 p1 ...))
 (let ((x1 (read f1))))
 (when x1)
 (multiple-value-bind (a2 b2 p2) (foo2))
 (with-open-file (f2 p2 ...))
 (let ((x2 (read f2))))
 (when x2)
 (bar x1 x2 ...))

В CL это одна строчка, натурально:

(defmacro nest (&rest r) (reduce (lambda (o i) `(,@o ,i)) r :from-end t))

Посмотреть страдания автора при попытке переизобрести такое в стандартной схеме, и потом в racket, можно по ссылкам:

https://news.ycombinator.com/item?id=15363475

https://fare.livejournal.com/189741.html

Самая приемлимая версия:

    (define-syntax (nest stx)
      (syntax-case stx ()
        ((nest outer ... inner)
         (foldr (lambda (o i)
                  (with-syntax (((outer ...) o)
                                (inner i))
                    #'(outer ... inner)))
                #'inner (syntax->list #'(outer ...))))))

И то, сходу даже и не понять что она делает, довольно нетривиальная вещь.

lovesan ★★
()
Ответ на: комментарий от no-such-file

Так будет?

Можно и так:

(define-syntax (loop stx)
  (syntax-case stx ()
    ((loop for var from n1 to n2 when cond collect res)
     (with-syntax ((it (datum->syntax stx 'it)))
       (syntax
        (for/list ([var (in-range n1 n2)]
                   #:when cond)
          (let ((it var))
            res)))))))

(define oddp odd?)

; ----- в REPL
> (let ((it 20)) (loop for i from 1 to it when (and (oddp i) i) collect it))

'(1 3 5 7 9 11 13 15 17 19)
monk ★★★★★
()
Ответ на: комментарий от monk

—– в REPL

Ну так ты покажи

(aif (+ 5 15) (loop for i from 1 to it when (and (oddp i) i) collect it))

Also есть же какие-то реализации loop на ракетке, давай лучше их, а то какой-то синтетикой попахивает.

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

Не вижу причин, почему реализация loop для Racket не могла б повторять семантику CL loop в вопросе collect it.

Хотя я не понимаю, почему ты не написал это проще:

(loop for i from 1 to it when (oddp i) collect i)
korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

почему ты не написал это проще

Странный вопрос. Потому что мне важно посмотреть поведение на it. Две анафоры разберутся, где чей it?

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от lovesan

Надо определить макрос, который бы позволял «линейно» писать код, как в сишечке, чтобы текст не сползал вправо постоянно.

Ну вот, изуродовал божественный, чётко структурированный, лисп говносишечкой. =) Как потом эту линейную хрень нормально анализировать и синтезировать? Ещё б инфиксную нотацию сюда впихнул, извращенец. =)

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

Я пока бегло глянул, но, может, ты не совсем понял, о каких проблемах писал автор в ЖЖ.

Он имел в виду это (в его простой наивной имплементации всё работало как у тебя):

    (defmacro nest (&rest r) (reduce (lambda (o i) `(,@o ,i)) r :from-end t))
     
    (format t "~s~%"
     (macroexpand-1
      '(nest
        (let ((x 1)))
        (case x)
        ((2) (format t "2~%"))
        ((1) (format t "1~%")))))

=>

(LET ((X 1))
  (CASE X
    ((2) (FORMAT T "2~%") ; <- non-closed case
    ((1) (FORMAT T "1~%")))))
korvin_ ★★★★★
()
Ответ на: комментарий от no-such-file

Ладно, ради интереса поставил таки ракетку и нашёл там «эталонный» loop

~ ❯❯❯ racket                                                                                                
Welcome to Racket v7.6.
> (require (planet jphelps/loop))
> (loop for i from 1 to 10 collect i)
'(1 2 3 4 5 6 7 8 9 10)
> (define-syntax (aif x)
    (syntax-case x ()
      ((aif cond then else)
       (with-syntax ((it (datum->syntax x 'it)))
         (syntax
          (let ((it cond))
            (if it then else)))))))
> (define oddp odd?)
> (aif (+ 5 15) (loop for i from 1 to it when (and (oddp i) i) collect it) nil)
'(20 20 20 20 20 20 20 20 20 20)

Эпично, чуваки.

no-such-file ★★★★★
()
Ответ на: комментарий от lovesan

(defmacro nest (&rest r) (reduce (lambda (o i) `(,@o ,i)) r :from-end t))

Вот идентичный однострочник на схеме:

(define-syntax (nest stx)
  (syntax-case stx ()
    ((nest r ...)
     (datum->syntax stx (reduce-right (lambda (o i) `(,@o ,i)) #f (syntax->datum #'(r ...)))))))

На Racket можно

(define-macro (nest . r) (reduce-right (λ (o i) `(,@o ,i)) #f r))
monk ★★★★★
()
Ответ на: комментарий от korvin_

На Racket однострочник. Впрочем:

(define-syntax (nest stx) (syntax-case stx () ((nest r ...) (datum->syntax stx (reduce-right (lambda (o i) `(,@o ,i)) #f (syntax->datum #'(r ...)))))))

тоже однострочник :-)

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

отсюда же

Это я уже проверил. Ок, как PoC сойдёт. Но интересно, насколько это жизнеспособно в боевых условиях. Вот «полноценный» loop оказался без it, а почему собственно? Слишком сложно? Я понимаю конечно, что «ненужно», но раз уж такие высокотеоретические разговоры пошли, то есть подозрение, что с практической точки зрения пилить всю эту гигиену в реальном сложном dsl получается не сильно проще, чем по-дидовски намутить парсер в cl. Отсутствие полного loop как бы намекает. Нельзя ли туда по-быстрому вкорячить it (во славу гигиены)?

PS: Кстати, я расчитывал, что

(aif (+ 5 15) (loop for i from 1 to it when (and (oddp i) (+ i it)) collect it) nil)

Тоже будет работать, но нет. Такой себе PoC всё-таки, ну да пёс с ним.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 5)
Ответ на: комментарий от lovesan

Глянул подробней, да, проблема с приоритетом, в случае case nest пихается внутрь case и тот выдаёт ошибку синтаксиса, т.к. пытается раскрыться первым.

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

Впрочем, всё равно не совсем ясно, что делать с case и т.п. (в том числе и в CL).

Либо

(nest
  (let ((x 1)))
  (case x)
  (((2) (format t "2~%"))
   ((1) (format t "1~%"))))

что не очень красиво

либо

(nest
  (let ((x 1)))
  (case x
   ((2) (format t "2~%"))
   ((1) (format t "1~%"))))

тогда проблем нет и в Racket с простой наивной реализацией.

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

И непонятно, что должно быть в таком случае:

(nest
  (let ((x 1)))
  (case x)
  (((2) (format t "2~%"))
   ((1) (format t "1~%")))
  (foo bar ...))

По логике nest выражение (foo bar ...) должно быть внутри case. И что должно в итоге получиться?

korvin_ ★★★★★
()
Ответ на: комментарий от no-such-file

Тоже будет работать, но нет.

Можно и такой:

(define-syntax (loop stx)
  (syntax-case stx ()
    ((loop for var from n1 to n2 when cond collect res)
     (with-syntax ((it (datum->syntax stx 'it)))
       (syntax
        (for*/list ([var (in-range n1 n2)]
                    [it (in-value var)]
                    #:when cond)
          res))))))
monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Отсутствие полного loop как бы намекает. Нельзя ли туда по-быстрому вкорячить it (во славу гигиены)?

В смысле в jphelps/loop ?

Тогда укажи, что в нём должно быть, например, для (loop for i from 1 to 5 for j from 2 to 6 collect it).

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

Тогда укажи, что в нём должно быть, например, для

(loop for i from 1 to 5
      for j from 2 to 6
      collect it)

очевидно ж

(loop for i from 1 to 5
      for j from 2 to 6
      for k from 3 to 7
      for l from 4 to 8
      for m from 5 to 9
      collect (+ it that and-that and-another-one and-one-more))

=)

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

Так потоки и слабые ссылки добавили. Их тоже в стандарте нет.

Потоки и слабые ссылки - это необходимость. Первое в современном компьютинге - точно. Call/cc - нет. Читай ниже, почему.

Даже в Си longjmp есть.

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

Любое изменение контекста - это удар коленом по печени производительности. Такое сильное, что даже в языке с плохим компилятором это заметишь. По этой же причине green threads не прижились: практического смысла нет.

Последнюю систему, в которой я массовую обработку в микроконтекстах видел - это дисковые массивы XtremIO. На SAS SSD оно ещё как-то шевелилось, причём постоянно cache cold было, но для NVMe уже всё: не годится.

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

А как же Erlang и горутины?

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

Я интереса ради смотрел их GC - очень примитивный, не рассчитан на долговременную работу процесса. В SBCL GC кажется космической ракетой.

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

Я интереса ради смотрел их GC - очень примитивный, не рассчитан на долговременную работу процесса.

Они ж вроде на low-latency (pause) затачивали, читал блог какого-то чела от java-мира как раз, что есть у этого своя цена, потому в джаве сохраняют разные варианты GC под разные задачи и для raw throughput и долговременных сервисов старый добрый serial GC всё ещё лучше новомодных GC1/Shenandoah/ZGC (в JVM мире по крайней мере), расчитанных на low pause.

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

BTW, ты ж вроде Lispworks юзал когда-то, уже всё, на SBCL перешёл?

Я с лиспом дел не имею с 2012 года :) По лиспоработе юзал LW - отличная штука!

Интереса ради ковырял SBCL, в том числе, его GC. От паровоза современных компиляторов, к сожалению, он отстал.

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

Тогда укажи, что в нём должно быть

Дурачка-то выключи, collect it всегда относится к when.

Например

>  (loop for i from 1 to 5 for j from 2 to 6 when (and (oddp (+ i j)) (* i j)) collect it)                         
(2 6 12 20 30)
no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от monk

Не работает же

> (let ((it 20)) (loop for i from 1 to it when (and (oddp i) (+ i it)) collect it)))                                           
'(1 3 5 7 9 11 13 15 17 19)

А нужно

> (let ((it 20)) (loop for i from 1 to it when (and (oddp i) (+ i it)) collect it))
(21 23 25 27 29 31 33 35 37 39)
no-such-file ★★★★★
()
Ответ на: комментарий от mv

LW - отличная штука!

Мне их CAPI (для GUI) нравился отчасти, но, к сожалению, Personal edition заморозилась на версии 6.? (а Professional уже 7.?) и не работает на x64 macOS, пощупать никак. =(

А инструменты Allegro (Franz.com) ты не юзал? Там у них и объектная БД и graph-store для CL, но это вроде не по твоей сфере. =)

Интереса ради ковырял SBCL, в том числе, его GC. От паровоза современных компиляторов, к сожалению, он отстал.

Именно в части GC или вообще?

korvin_ ★★★★★
()
Ответ на: комментарий от no-such-file

А нужно

Как у тебя (and (oddp i) (+ i it)) возвращает ложь для i = it = 1? (and t (+ 1 1)) определённо должно быть не nil.

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

Дурачка-то выключи, collect it всегда относится к when.

Внезапно. Из твоих предыдущих сообщений это никак не следовало.

То есть it существовать должен только, если есть условие when?

А если их больше одного:

(loop for i from 1 to 10 
      when (and (< i 8) (+ i 2)) 
      when (and (> i 3) (+ i 1)) 
      collect it) 

?

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

Из твоих предыдущих сообщений это никак не следовало

Здрастье, приехали. Я про обычный loop из cl. Подробности в гиперспеке.

А если их больше одного

> (loop for i from 1 to 10 
      when (and (< i 8) (+ i 2)) 
      when (and (> i 3) (+ i 1)) 
      collect it) 
(5 6 7 8)

>  (loop for i from 1 to 10 when (and (< i 5) 1) collect it when (and (> i 5) 2) collect it)
(1 1 1 1 2 2 2 2 2)

> (loop for i from 1 to 10 when (and (< i 8) 1) collect it when (and (> i 2) 2) collect it)                                 
(1 1 1 2 1 2 1 2 1 2 1 2 2 2 2)

Улавливаешь?

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от korvin_

Allegro не юзал.

SBCL отстал от GCC где-то в начале 4.x (у GCC), когда у последнего SSA появилось.

mv ★★★★★
()
Ответ на: комментарий от no-such-file

Держи: https://github.com/Kalimehtar/cl-loop

От jphelps/loop добавил макрос acond и в тексте loop.rkt в двух местах заменил cond на acond.

Множественный collect не реализовывал

#lang racket
(require "loop.rkt")

(define-syntax (aif x)
  (syntax-case x ()
    ((aif cond then else)
     (with-syntax ((it (datum->syntax x 'it)))
       (syntax
        (let ((it cond))
          (if it then else)))))))

(aif (+ 5 15) (loop for i from 1 to it when (and (odd? i) i) collect it) #f)
----------
'(1 3 5 7 9 11 13 15 17 19)
monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от silver-bullet-bfg

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

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

Несмотря на овердофига комментариев в треде, других плюсов и минусов нет. То, что можно написать на схеме, с таким ж успехом можно написать на общелиспе, и наоборот.

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

То, что можно написать на схеме, с таким ж успехом можно написать на общелиспе, и наоборот.

Если брать схему, то да, разница только в гигиене. Если брать Racket, то в нём есть syntax-local-lift-require (добавляет результат макроса в импортируемые модули), syntax-local-lift-module-end-declaration (добавляет результат в конец текущего модуля), make-set!-transformer (позволяет привязать к символу чтение и запись: в CL есть symbol-macrolet, но он только локально и не даёт переназначить setq).

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

(define-cbr (f a b)
  (swap a b))
 
(let ([x 1] [y 2])
  (f x y)
  (list x y)) 
=> '(2 1)
monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.