LINUX.ORG.RU

Коммон лисп. Multiple dispatch как в Julia

 


0

1

Привет. В Julia при определении функции наподобие такой

f(x :: T) where T <: Number = 2x

Я могу вызвать f() с любым числом и компилятор сам подставит код, наиболее специфичный для данного типа.

А в common lisp

(defun f (x) (declare (type number x)) (* 2 x))

Компилятор создаст 1 функцию с наименее специфичным кодом, которая будет работать с любым числом

Как можно эмулировать поведение джулии с заранее известным множеством типов?

UPD: название треда вышло дурацким

А где тут multiple, если у тебя функция с одним аргументом? Вообще typecase даёт возможность сделать диспетчеризацию по типам, а использование inline (никогда особо её не использовал, поэтому не посоветую, в каких случаях можно гарантировать её применение), или, ещё лучше, использование макросов, позволяют сократить число вызовов функций. В SBCL компилятор будет использовать инфу о типе, известную в момент вызова, и выбрасывать заведомо ненужное. Но проверяется это в любом случае с помощью disassemble. В CLOS обычно всё медленно. Можно поизвращаться с метаобъектным протоколом и выжать больше скорости из CLOS, я читал какие-то статьи на эту тему, но сам бы так делать не стал.

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 2)

Коммон лисп. Multiple dispatch как в Julia

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

P.S. а в ОП вообще о другом спрашивается. Ну ты мастер заголовков. На журналиста учился?

Virtuos86 ★★★★★ ()
Последнее исправление: Virtuos86 (всего исправлений: 1)

Если компиляторы борщелиспа умеют выполнять мономорфизацию кода (это то, что ты хочешь), то осуществимо. Но для мономорфизации нужна поддержка статической диспетчеризации в ЯП, а лишп динамичный же. В Тизпизированном Рэкете вот можно типы проставлять ручками, там наверняка такое есть.

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

А ты совсем труевым стал, читаешь только заголовки. Но истинные тру не читают даже заголовки, а смотрят последний коммент и сразу срут в эту ветку. Бери на вооружение.

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

Маня, ответь на вопрос: если у ТС в заголовке топика один вопрос, а в теле поста он спрашивает про другое, это чьи проблемы? А в итоге окажется, что ему нужно третье :)

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

Для генерации эффективного кода.

Так он и так оптимизирует. Подставит в место вызова (* 2 x) и выберет реализацию умножения на базе выведенного типа x. Вплоть до того, что для константы сразу результат подставит.

В принципе, эквивалент «Я могу вызвать f() с любым числом и компилятор сам подставит код, наиболее специфичный для данного типа».

monk ★★★★★ ()
Ответ на: комментарий от monk
(defun foo (x)
  (declare (type single-float x)
           (optimize (speed 3)))
  (* 2 x))

(defun bar (x)
  (declare (type number x)
           (optimize (speed 3)))
  (* 2 x))

(disassemble 'foo)
; disassembly for FOO
; Size: 23 bytes. Origin: #x2273805F                          ; FOO
; 5F:       F30F58C9         ADDSS XMM1, XMM1
; 63:       660F7ECA         MOVD EDX, XMM1
; 67:       48C1E220         SHL RDX, 32
; 6B:       80CA19           OR DL, 25
; 6E:       488BE5           MOV RSP, RBP
; 71:       F8               CLC
; 72:       5D               POP RBP
; 73:       C3               RET
; 74:       CC10             INT3 16                          ; Invalid argument count trap
NIL
* (disassemble 'bar)
; disassembly for BAR
; Size: 20 bytes. Origin: #x227380F7                          ; BAR
; 0F7:       BF04000000       MOV EDI, 4
; 0FC:       FF1425B800B021   CALL QWORD PTR [#x21B000B8]     ; GENERIC-*
; 103:       488BE5           MOV RSP, RBP
; 106:       F8               CLC
; 107:       5D               POP RBP
; 108:       C3               RET
; 109:       CC10             INT3 16                         ; Invalid argument count trap
NIL
* 

Нет разницы по твоему?

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

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

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

покажи, во что компилирует Julia

Он хочет перегрузку по типу. Чтобы, например для целых компилялось x<<1 (SHL), а для вещественных x*2 (FMUL).

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

Он хочет перегрузку по типу. Чтобы, например для целых компилялось x<<1 (SHL), а для вещественных x*2 (FMUL).

Она и так есть.

$ cat test.lisp
(declaim (optimize (speed 3)) (inline f))
(defun f (x) (declare (type number x)) (* 2 x))
(defun bar (x) (declare (type single-float x)) (f x))
(defun baz (x) (declare (type fixnum x)) (f x))
$ sbcl
* (compile-file "test")
...
* (load "test")
T
* (disassemble 'f)
; disassembly for F
; Size: 18 bytes. Origin: #x52BBAFD7
; D7:       BF04000000       MOV EDI, 4                       ; no-arg-parsing entry point
; DC:       E84F5B54FF       CALL #x52100B30                  ; GENERIC-*
; E1:       488BE5           MOV RSP, RBP
; E4:       F8               CLC
; E5:       5D               POP RBP
; E6:       C3               RET
; E7:       CC0F             BREAK 15                         ; Invalid argument count trap
NIL
* (disassemble 'bar)
; disassembly for BAR
; Size: 23 bytes. Origin: #x52BBAF3F
; 3F:       F30F58C9         ADDSS XMM1, XMM1                 ; no-arg-parsing entry point
; 43:       660F7ECA         MOVD EDX, XMM1
; 47:       48C1E220         SHL RDX, 32
; 4B:       80CA19           OR DL, 25
; 4E:       488BE5           MOV RSP, RBP
; 51:       F8               CLC
; 52:       5D               POP RBP
; 53:       C3               RET
; 54:       CC0F             BREAK 15                         ; Invalid argument count trap
NIL
*  (disassemble 'baz)
; disassembly for BAZ
; Size: 24 bytes. Origin: #x52BBAEA9
; A9:       48D1E2           SHL RDX, 1                       ; no-arg-parsing entry point
; AC:       48D1E2           SHL RDX, 1
; AF:       7108             JNO L0
; B1:       48D1DA           RCR RDX, 1
; B4:       E8775F54FF       CALL #x52100E30                  ; ALLOC-SIGNED-BIGNUM-IN-RDX
; B9: L0:   488BE5           MOV RSP, RBP
; BC:       F8               CLC
; BD:       5D               POP RBP
; BE:       C3               RET
; BF:       CC0F             BREAK 15                         ; Invalid argument count trap
NIL

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

Ну, defmethod стандарт, по идее.

defmethod производит диспетчеризацию по классам, а не по типам.

Соответствующий класс в clisp называется float. То есть работает

(defmethod foo ((x float)) ...)

Узнать класс можно через (class-of 1.0)

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

Вы с вертихуем оба дебилы (растишки, почему я не удивлен). ТС не просит родить ему компилятор лиспа с крестовыми шаблонами или просветить в вопросах терминологии, он ищет воркараунд для конкретной задачи. Если вы не знаете CL какого буя вы кукарекаете? Это не толксы блджад.

anonymous ()
Ответ на: комментарий от monk
(defun bar (x) (declare (type single-float x)) (f x))
(defun baz (x) (declare (type fixnum x)) (f x))

Так он хочет чтобы было просто foo и в компайлтайм выбирался нужный вариант в зависимости от типа аргумента. Чтобы (foo 2) компилялось в одно, а (foo 2.0) в другое.

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

Так он хочет чтобы было просто foo и в компайлтайм выбирался нужный вариант в зависимости от типа аргумента

Я ведь это и показываю. Что внутри bar (f x) скомпилировалось в одно, а внутри baz – в другое.

Чтобы (foo 2) компилялось в одно, а (foo 2.0) в другое.

А это вообще в константу скомпилируется.

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

Как можно эмулировать поведение джулии с заранее известным множеством типов?

Написать (declaim (inline foo)) перед определение foo. Тогда он будет перекомпилироваться по месту вызова.

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

Ты не в ту сторону воюешь, не надо мне дурачка включать.

Ты просто предлагал typecase включать. Если для оптимизации, то это явно лишнее. Он там и так уже есть (на уровне функции *).

Ты ТСу распиши как это конкретно сделать

Я расписал. А он всё равно молчит.

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

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

Helper macro для такого поднимания можно нарисовать, например, так:


(defmacro with-types ((varname &rest types) &body body)
  `(etypecase ,varname
     ,@(loop for type in types
	     collect `(,type (let ((,varname (the ,type ,varname)))
			       ,@body)))))
...
(defun bar ()
  (declare (optimize (speed 3)))
  (let ((a (foo)))
    (with-types (a fixnum float t)
      (+ a 1))))

Я бы в такой ситуации код писал по-человечески, а в нужных местах добавил define-compiler-macro и уж в нём бы извращался.

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

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

Ну да, не сообразит. Хотя мог бы: для любой новой переменной вытаскивать вверх все условия проверки типа из всего дерева вызовов функций. Вполне автоматизируется. Вроде даже в Stalin было реализовано.

monk ★★★★★ ()