LINUX.ORG.RU

clojure после common lisp - насколько омерзительно и в чём именно?

 , ,


3

2

Всё ещё думаю о возможности заняться разработкой на clojure. Но ява же тормозная - меня бесит скорость сборки. Вот боюсь, как бы кложа не оказалась слишком уж тормозной.

Расскажите о своих ощущениях (из серии «собираюсь жениться - отговорите»).

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

Тебе не нравится, что :use по-дефолту так работает? Ну так здесь так специально сделано, именно для того, чтобы было обозначенное тобой поведение.

Я перестал пытаться «починить» Common Lisp, когда наткнулся на Racket. Там про имена, которые не используешь можно не беспокоиться вообще. Я могу в своём модуле написать

(define list (a b c) (format "~a,~a,~a" a b c)) 

и точно знать, что ничего нигде не сломается. В минусе то, что нет явной формы p1.f1 = ... на чужой модуль, но это позволяет делать оптимизации при компиляции: если нечто является константой, компилятор может это использовать. Если очень надо экспортировать в стиле CL / python, можно экспортировть хэш с функциями.

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

import numpy as np

Вот именно: as np. Но в CL такой формы нет.

А чем тебе local-nicknames не нравятся?

Я пытался сделать https://github.com/Kalimehtar/advanced-readtable , но интеграция со SLIME страдала.

Сейчас, вроде бы, все норм с поддержкой в slime у local-nicknames

Да и судя по количеству звёзд не особо и нужно.

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

local-nicknames решает эту проблему.

Проблема звезд в том, что никто не узнал о твоем пакете тогда. На реддит постил? Я минимум несколько проектов видел на схожую тему и по-моему они появились на гитхабе позже твоего (но это не точно). В любом случае, такие вещи мне тоже кажутся очевидными.

Их там таких десятка полтора. Возьми hu.dwim получишь ещё полсотни слов «стандарта».

Про hu.dwim оч спорно в качестве стандарта :)

макрос (iterate … должен внутри своих скобочек создавать скоуп? Как именно?

Мой advanced-readtable предлагает push-local-package.

В чем суть в двух словах?

после (lock-package (find-package :p1)) будешь получать эрроры

Угу. Включая локальные переменные в let и локальные функции в flet и labels.

Если ты находишься в залоченном пакете? Надо будет проверить.

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

Я перестал пытаться «починить» Common Lisp, когда наткнулся на Racket.

Кроме пакетов тебе в нем что-то еще решительно не нравится? Макросы, lisp-2? Ну, то есть что принципиально? Пакеты как раз можно починить.

(define list (a b c) (format «~a,~a,~a» a b c))
и точно знать, что ничего нигде не сломается

А в CL что должно сломаться в аналогичном примере?

В минусе то, что нет явной формы p1.f1 = ... на чужой модуль, но это позволяет делать оптимизации при компиляции

Я предпочел бы допилить CL, там где это возможно. Чем пытаться разобраться с _ограничениями_, которые не факт что можно обойти.

Если очень надо экспортировать в стиле CL / python, можно экспортировть хэш с функциями.

У этого подхода есть подводные камни?

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

Про hu.dwim оч спорно в качестве стандарта :)

Ну можешь отдельные куски его. Типа defstar, perec, … Их тоже сложно использовать не импортируя.

В чем суть в двух словах?

(push-local-package 'iterate:iter :iterate)
;;; теперь внутри (iterate:iter ....) видны все экспортные символы пакета iterate.

Если ты находишься в залоченном пакете? Надо будет проверить.

Если используешь залоченный пакет.

(labels ((list (a b) (cons a b)) (list a b)) ; выдаёт ошибку
monk ★★★★★
()
Ответ на: комментарий от alienclaster

Кроме пакетов тебе в нем что-то еще решительно не нравится? Макросы, lisp-2? Ну, то есть что принципиально? Пакеты как раз можно починить.

Система сборки и принципиальное отсутствие call/cc (либо корутин, либо генераторов). До Racket я пытался сделать «правильный Common Lisp» из advanced-readtable + xcvb + cl-fiber (затем cl-async, так как cl-cont, на основе которого сделан cl-fiber, жуткий тормоз).

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

А в CL что должно сломаться в аналогичном примере?

Если нет блокировки пакетов, то сломается всё, так как переопределится CL:LIST. Возможно, включая компилятор.

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

У этого подхода есть подводные камни?

Только необходимость автора модуля явно разрешить изменение функций в нём (можно не всех). Ну и синтаксически: вместо (f2 3) придётся писать ((p1 f2) 3).

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

я пытался сделать «правильный Common Lisp» из…

xcvb

Похоже что Вам просто не нужен Common Lisp. Racket под Ваши требования явно подходит лучше. Зачем натягивать сову на глобус?

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

(define list (a b c) (format «~a,~a,~a» a b c))

А в CL что должно сломаться в аналогичном примере?

(defpackage abc
  (:use :cl))

(in-package abc)

(defvar list 42) ; норм

(defun list (omg) omg) ; Ошибка о залоченном пакете, либо переопределение стандартной функции list
Gentooshnik ★★★★★
()
Ответ на: комментарий от Gentooshnik

(defun list (omg) omg)
Ошибка о залоченном пакете, либо переопределение стандартной функции list

Так ведь так и _должно_ быть или я что-то не понял? И, кстати, что не так с xcvb - если можно с конкретикой?

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

Макросы, lisp-2? Ну, то есть что принципиально? Пакеты как раз можно починить.

Система сборки и принципиальное отсутствие call/cc

Я практически ни разу не использовал продолжения, наверное, это ужасно - но действительно не было задач. А полноценные продолжения кажутся «плохой вещью». Есть delimited - но я хз, в ракете вроде бы полноценный call/cc, что на мой взгляд плохо. Но не могу сказать, что до конца разобрался в вопросе.

(либо корутин, либо генераторов).

Генераторы есть в том или ином виде в либах, в запчастях других либ, ну и на крайняк же «можнасделать» через ленивость, которую тоже этосамое. К вопросу корутинг - видел какие-то реализации поверх cl-cont, но он тормозит. А cl-async не решает твоих проблем разве?

Если нет блокировки пакетов, то сломается всё, так как переопределится CL:LIST. Возможно, включая компилятор.

Запрет на изменение стоит же, да его можно обойти. Но по-дефолту ты ничего не сломаешь.

Только необходимость автора модуля явно разрешить изменение функций в нём (можно не всех).

А если он явно не разрешил то что нам делать?

Ну и синтаксически: вместо (f2 3) придётся писать ((p1 f2) 3).

Страшненько. Хотя лисп-2 тоже страшно.

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

Ну можешь отдельные куски его. Типа defstar, perec, … Их тоже сложно использовать не импортируя.

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

;;; теперь внутри (iterate:iter ....) видны все экспортные символы пакета iterate.

А с local-nicknames оно не так?

(labels ((list (a b) (cons a b)) (list a b)) ; выдаёт ошибку

Под «используешь» ты имеешь ввиду мы его через :use включили в другой пакет? Сейчас под рукой нет CL, вечером чекну.

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

Похоже что Вам просто не нужен Common Lisp. Racket под Ваши требования явно подходит лучше. Зачем натягивать сову на глобус?

Я именно это и написал. В тот момент, когда я обнаружил Racket, ощущение было, что всё, что я хотел от языка программирования, уже сделано (и сверху добавлены гигиенические макросы).

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

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

В питоне генераторы или в js async/await тоже никогда? Это ведь то же самое.

в ракете вроде бы полноценный call/cc, что на мой взгляд плохо

В ракете они все: call/cc, call/ec, call/comp.

А cl-async не решает твоих проблем разве?

Решает, хотя и хуже. И к тому моменту я наткнулся на Racket, где всё было решено гораздо лучше.

Запрет на изменение стоит же, да его можно обойти.

На CL стоит. А, например, на iterate - нет.

А если он явно не разрешил то что нам делать?

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

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

А с local-nicknames оно не так?

Там такой функции, насколько я знаю, нет. Можно только переименовать iterate в i для удобства.

Под «используешь» ты имеешь ввиду мы его через :use включили в другой пакет? Сейчас под рукой нет CL, вечером чекну.

Да. Блокировке всё равно, меняешь через defun или labels. На самом деле, это имеет смысл, так как макросы негигиенические и любой макрос внутри (labels ((list …)) ….) может повести себя неожиданно.

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

В питоне генераторы или в js async/await тоже никогда? Это ведь то же самое.

в питоне генераторы постоянно использую, но я не уверен, что это «то же самое», что и полноценный call/cc.

На CL стоит. А, например, на iterate - нет.

И что? Никто не мешает изменить этот дефолт.

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

в питоне генераторы постоянно использую, но я не уверен, что это «то же самое», что и полноценный call/cc.

Имея генераторы можно сделать процентов 90 того, что можно сделать с call/cc. Причём остальные 10 — это реально экзотика.

И что? Никто не мешает изменить этот дефолт.

Можно. В advanced-readtable у меня где-то была такая опция. И den73, насколько я помню, делал блокировку чужих пакетов по-умолчанию и iterate с keyword’ами, а не экспортированными символами. Проблема в количестве костылей.

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

Имея генераторы можно сделать процентов 90 того, что можно сделать с call/cc. Причём остальные 10 — это реально экзотика.

Так да, поэтому я и говорю - нужен ли call/cc вообще? А генераторы в CL можно сделать. У полноценного call/cc есть и побочные эффекты (unwind-protect).

В advanced-readtable у меня где-то была такая опция.

В итоге ты не допилил CL под себя? Просто в рекет было все готово или на какие-то принципиальные ограничения наткнулся?

делал блокировку чужих пакетов по-умолчанию и iterate с keyword’ами, а не экспортированными символами. Проблема в количестве костылей.

Если в итоге это выглядит нормально - то в чем собственно проблема? Какая разница что там внутри.

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

Тред не читал, поэтому возможно повторюсь, но

  • продолжения
  • генераторы
  • корутины
  • грин треды

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

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

либо потоками (что тормозит ещё больше).

Есть ещё третий вариант: использовать внешние грин треды как cl-async.

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

Просто в рекет было все готово или на какие-то принципиальные ограничения наткнулся?

Было всё готово. Технически, глядя на Racket, я вполне мог сделать реализацию модулей в CL (через лямбду с хешем внутри). Раздельная компиляция в CL есть через XCVB. Аналог зелёных потоков - cl-async. Статическую типизация на макросах всё равно в каком языке делать. GUI – gtk-cffi у меня уже работал.

Ещё из принципиальных ограничений была гигиеничность макросов, но на тот момент я не осознавал вообще необходимость гигиены у макроса: на большую часть примеров хватало gensym.

Если в итоге это выглядит нормально - то в чем собственно проблема? Какая разница что там внутри.

Костыли протекают. Мой advanced-readtable конфликтовал с русскоязычными символами. XCVB для компиляции запускает по лиспу на пакет. При использовании GTK-CFFI сборщик мусора теряет объекты лиспа, если на них ссылки только изнутри объектов GTK. Финализатор в SBCL не может читать удаляемую ссылку. Со всем этим можно смириться и привыкнуть, но если рядом есть нормальная реализация, смириться гораздо сложнее.

Тот же den73 бросил лисп, когда выяснил, что в SBCL типизация ненадёжная и ничего делать с этим разработчики не хотят.

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

Технически, глядя на Racket, я вполне мог сделать реализацию модулей в CL (через лямбду с хешем внутри)

Можешь подробнее рассказать, в чем суть подхода?

Раздельная компиляция в CL есть через XCVB.

xcvb заброшен, asdf развивается.

Статическую типизация на макросах всё равно в каком языке делать

Нужна ли в CL полноценная статическая типизация? Qi же был, не особо кому нужен. И если она будет - то тоже подмножеством, как typed racket.

когда выяснил, что в SBCL типизация ненадёжная и ничего делать с этим разработчики не хотят

Что значит ненадежная?

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

А что, кстати, не так со snakes в разрезе генераторов? И часто ли они действительно нужны или можно обойтись замыканиями?

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

А что, кстати, не так со snakes в разрезе генераторов?

:depends-on (#:cl-cont

Жуткие тормоза.

И часто ли они действительно нужны или можно обойтись замыканиями?

Они удобны. Итераторы в питоне сплошь на генераторах. А на замыканиях попробуй написать:

(defgenerator some-numbers ()
    (loop for i from 1 upto 3 do (yield i))
    (print 'some-message)
    (loop for i from 8 upto 9 do (yield i)))

Написать можно, но придётся явным образом состояние сохранять.

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

Можешь подробнее рассказать, в чем суть подхода?

(defun my-module ()
  (let ((res (make-hash-table)))
    (setf (gethash 'f1 res) #'f1) 
    (setf (gethash 'f2 res) #'f2)
    ...
    res))

(defun import (module)
  (maphash (lambda (k v) (setf (symbol-function (local-symbol k)) v) v)))

xcvb заброшен, asdf развивается.

Так технология существует. Но действительно тащить пачку проектов в одиночку — глупо.

Qi же был, не особо кому нужен.

Насколько я помню, из него произвольную функцию CL вызвать нельзя было.

И если она будет - то тоже подмножеством, как typed racket.

А чем это плохо? Или что значит «подмножеством»?

Что значит ненадежная?

это баг в SBCL или я что-то не понял?

Доводы за раздельные неймспейсы для функций и переменных в CL (комментарий)

Где-то ещё было про declare на типизированную коллекцию, а затем изменение типа элемента этой коллекции.

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

Тот же den73 бросил лисп, когда выяснил, что в SBCL типизация ненадёжная и ничего делать с этим разработчики не хотят.

А в какой-то реализации надёжная?

UPD: Отвечу сам себе. По опыту портирования библиотеки с SBCL на CCL можно заключить что в CCL - достаточно надёжная. Я его потом как статический анализатор кода использовал.

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

CCL - достаточно надёжная

? (declaim (optimize (speed 3) (safety 0) (compilation-speed 0) (space 0) (debug 0)))
NIL
? (declaim (ftype (function (fixnum ) fixnum) FOO ))
NIL
? (defun FOO  (X  )
 (declare (type FIXNUM  X ))
  (+ X 1)
  )
FOO
? (declaim (ftype (function (NUMBER ) NUMBER) BAR ))
NIL
? (defun BAR (X)
  (declare (type NUMBER X))
  (FOO X)
  )
BAR
? (compile 'foo)
FOO
NIL
NIL
? (compile 'bar)
BAR
NIL
NIL
? (bar most-positive-fixnum)
1152921504606846976
? (bar (1+ most-positive-fixnum))
#<BOGUS object @ #x21004DD995>

Я его не умею готовить? Где хотя бы предупреждение, что что-то не так.

Причём этот BOGUS object и дальше в любой арифметике без ошибок участвует.

? (type-of  (bar (1+ most-positive-fixnum)))
FIXNUM

Ужас, однако.

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

Так технология существует. Но действительно тащить пачку проектов в одиночку — глупо.

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

Qi же был, не особо кому нужен.

Насколько я помню, из него произвольную функцию CL вызвать нельзя было.

Это был отдельный язык реализованный поверх CL емнип.

И если она будет - то тоже подмножеством, как typed racket.

А чем это плохо? Или что значит «подмножеством»?

Не могу сказать, что это «плохо», но подход, когда можно в одном модуле писать на, условно, типизированном CL и нетипизированном мне больше нравится, чем то, как это сделано в racket. Если нужен вывод типов и настоящая статическая типизация, я бы хотел это видеть на уровне ф-ции, а не на уровне модуля.

Где-то ещё было про declare на типизированную коллекцию, а затем изменение типа элемента этой коллекции.

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

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

А что, кстати, не так со snakes в разрезе генераторов?

Жуткие тормоза.

В сравнении с чем? Ты проводил какие-то замеры, допустим, CL vs python в данном контексте? Если тормозит на уровне питон - имхо это не так уж и плохо.

Они удобны. Итераторы в питоне сплошь на генераторах. А на замыканиях попробуй написать:

Ну iterate же как-то написан тем не менее :)

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

C SAFETY 3 - ошибка в рантайме. И

? (defun bar () (foo (1+ most-positive-fixnum)))
;Compiler warnings :
;   In BAR: Type declarations violated in (THE FIXNUM 1152921504606846976)
BAR

Т.е. у компилятора всё нормально, а интерпретатор ничего не проверяет.

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

Если нужен вывод типов и настоящая статическая типизация, я бы хотел это видеть на уровне ф-ции, а не на уровне модуля.

А принудительные контракты вешать на границе каждой функции? Тормозить ведь будет адски.

И никто не мешает завернуть функцию в субмодуль. Можно даже написать для этого макрос (define-untyped …).

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

В сравнении с чем? Ты проводил какие-то замеры, допустим

(cl-cont:defun/cc list-cc (n) (loop for i from 1 to n collect i))
(defun list-n (n) (loop for i from 1 to n collect i))

(time (dotimes (k 10000) (list-cc k) nil))
Evaluation took:
  26.321 seconds of real time

(time (dotimes (k 10000) (list-n k) nil))
Evaluation took:
  1.052 seconds of real time

Питон в среднем в 10 раз медленнее лиспа.

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

Если нужен вывод типов и настоящая статическая типизация, я бы хотел это видеть на уровне ф-ции, а не на уровне модуля.

А принудительные контракты вешать на границе каждой функции? Тормозить ведь будет адски.

Не совсем понял, при чем тут контракты, мы вроде про статическую типизацию (или ты конкретно о том, как устроен интерекшн в ракете динамических и типизированных модулей?). Есть типизированная ф-ция мы в ней все выводим, чекаем и оптимизируем. Рядом нетипизированная, из нее можем вызывать типизированную. Про наоборот неуверен - потому что весь смысл теряется, либо как в рекет - require / typed предварительно все протипизировав вручную (думаю, это не всегда возможно). И не в курсе, можно ли там протипизировать ф-цию как возвращающую Int _или_ String.

На счет принудительных контрактов - мне кажется, это неверная стратегия. Нам нужны настраиваемые стратегии в случае, что мы делаем при взаимодействии типизированного подмножества модуля (в идеале функции или типизированного выражения) и нетипизированного. Во-первых компилятор может попытаться сам все вывести из динамики по тому, как мы используем динамический код в статике, во-вторых (как в ракете) - мы указываем какие типы ожидаем, компилятор обвешивает контрактами, и лучше чтобы проверял он их по возможности в компайл-тайме и какие-нибудь небезопасные варианты тоже нужны вроде «выполни как получится без всяких проверок или если не можешь вывести / чекнуть - просто выполни и упади если что».

И никто не мешает завернуть функцию в субмодуль.

На сколько я помню у рекет весь модуль будет typed, что именно надо заворачивать, динамику? Если ты имеешь ввиду просто модуль (module ... блаблабла) то выглядит это страшненько каждую или через одну ф-цию так заворачивать + везде прописывать require / typed. Да, придется заворачивать в макросы.

Можно даже написать для этого макрос (define-untyped …).

Внутри define-untyped гипотетическом можно прописать декларации для отдельных выражений или у нас или-или (типизированный / нетпизированный модуль)?

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

Не совсем понял, при чем тут контракты, мы вроде про статическую типизацию

У тебя

(: f (Integer -> Integer))
(define (f x) ...)

(define (g y) .... (f y) ...)

Так как f типизирован, компилятор внутри него может быть уверен, что x имеет тип Integer и нигде его не проверять. Так как g нетипизирован, любой вызов f из g должен проверять все типы переданных аргументов. Иначе будет UB.

какие-нибудь небезопасные варианты тоже нужны вроде «выполни как получится без всяких проверок или если не можешь вывести / чекнуть - просто выполни и упади если что».

Для UB уже C++ выше крыши. Если реально надо, чтобы быстро работало и ничего не проверяло, лучше на нём. А если очень надо без проверок, в Racket для этого отдельные функции: unsafe-car, unsafe-fx+ и прочее. А запустить нормальную функцию выкинув проверки не выйдет (хочешь, форкай и сам отвечай за код без проверок).

На сколько я помню у рекет весь модуль будет typed, что именно надо заворачивать, динамику?

Можно модуль тайпед, а заворачивать динамику. Можно модуль динамический, а заворачивать тайпед. В racket каждый модуль может иметь свой язык.

Внутри define-untyped гипотетическом можно прописать декларации для отдельных выражений или у нас или-или (типизированный / нетпизированный модуль)?

А смысл от них, если внутри функции от них ничего не зависит? Запускать изнутри define-untyped типизированные функции? Так для этого всё равно придётся явно написать ту же проверку, которая есть неявно в контракте. А если она просто есть ещё раз явно, компилятор всё равно выкинет.

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

На сколько я помню у рекет весь модуль будет typed, что именно надо заворачивать, динамику?

Можно модуль тайпед, а заворачивать динамику. Можно модуль динамический, а заворачивать тайпед. В racket каждый модуль может иметь свой язык.

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

Внутри define-untyped гипотетическом можно прописать декларации для отдельных выражений или у нас или-или (типизированный / нетпизированный модуль)?

А смысл от них, если внутри функции от них ничего не зависит?

Хотя бы для оптимизации.

Запускать изнутри define-untyped типизированные функции? Так для этого всё равно придётся явно написать ту же проверку, которая есть неявно в контракте.

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

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

Проверил у себя, получаются такие результаты (real time):

(time (dotimes (k 10000) (list-cc k) nil))

;; => 7.706 sec

(time (dotimes (k 10000) (list-n k) nil))

;; => 0.477 sec

Т.е. «всего» в 15 раз медленней. А во сколько должно быть, вообще без оверхеда? В ракете именно так?

(defun list-snakes (n) (generator->list (with-yield (dotimes (i n)(yield i)))))
(time (dotimes (k 10000) (list-snakes k) nil))

;;=> 12.798 sec - медленнее в 25 раз.

Есть еще генераторы в iter, я не проверял как они работают. Имею ввиду (generate...).

Питон в среднем в 10 раз медленнее лиспа.

Генераторы тоже медленнее в 10 раз? Если да, то на мой взгляд это приемлемая скорость.

alienclaster ★★★
()
Ответ на: комментарий от alienclaster
import time, sys

def list_yield(n):
    for i in range(n):
        yield i

t1 = time.process_time()
for i in range(10000):
    y = []
    for k in range(10000):
        y.append(k)
t2 = time.process_time()

print('for tooks {} seconds'.format(t2-t1))

t1 = time.process_time()
for i in range(10000):
    y = []
    for k in list_yield(10000):
        y.append(k)
t2 = time.process_time()

print('yield tooks {} seconds'.format(t2-t1))

for tooks 12.230478399999999 seconds
yield tooks 18.876121 seconds

python 3.8.7 64 bit

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

Есть еще генераторы в iter, я не проверял как они работают. Имею ввиду (generate…).

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

Т.е. «всего» в 15 раз медленней. А во сколько должно быть, вообще без оверхеда? В ракете именно так?

В ракете без оверхеда. Там продолжение неявно виртуальной машиной хранится. А defun/cc тормозит тем больше, чем длиннее функция. Там нелинейная зависимость времени выполнения.

Генераторы тоже медленнее в 10 раз? Если да, то на мой взгляд это приемлемая скорость.

Раза в полтора-два. Там же не через CPS сделано, а через примитив виртуальной машины.

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

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

Это можешь вообще без проблем. Но для типизированного модуля автоматически контракты сгенерируются. Для сложных типов могут быть достаточно дорогие.

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

А смысл? Доказывать этот тип как на уровне выражения внутри функции? И зачем там нужен тип, если весь его смысл — проверка входящих в функцию данных (и описание гарантий каких-то свойств для значения функции)?

Я так понимаю, ты намекаешь, на Common Lisp’овый declare type. Так он там для оптимизаций, а не для типов. Проверка типов там дырявая.

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

Вот еще вариант с генератором из iterate:

(defun list-iter (n) (iter (generating i from 1 to n)(next i)(collect i)))
(time (dotimes (k 10000)(list-iter k) nil))
; => 0.480 second of real time

По-моему вполне приемлемая скорость.

При этом (time (dotimes (k 10000)(list-iter 10000) nil)) ; явно прописываем 10000 в параметр list-iter

; => 0.923 second of real time

И тут я, кстати, не понял почему так. Есть идеи?

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

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

Но работают они как генераторы, если утка крякает как генератор ну и так далее. Из функции нельзя вызвать например как?

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

А какая там сложность алгоритма? И почему именно от «времени выполнения» зависят тормоза?

Раза в полтора-два. Там же не через CPS сделано, а через примитив виртуальной машины.

Да, в полтора раза +-. А в ракете вообще 1 к 1 скорость? Можешь написать строчку как там генератор выглядит для нашего примера?

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

А смысл? Доказывать этот тип как на уровне выражения внутри функции? И зачем там нужен тип, если весь его смысл — проверка входящих в функцию данных (и описание гарантий каких-то свойств для значения функции)?

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

Я так понимаю, ты намекаешь, на Common Lisp’овый declare type. Так он там для оптимизаций, а не для типов. Проверка типов там дырявая.

Да, так я ж написал - для оптимизаций.

Проверка типов там дырявая.

Вопрос реализации имхо.

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

И тут я, кстати, не понял почему так. Есть идеи?

А, сори че-то тупанул. У нас же k меняется лол :)

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

Генераторы тоже медленнее в 10 раз? Если да, то на мой взгляд это приемлемая скорость.

Раза в полтора-два. Там же не через CPS сделано, а через примитив виртуальной машины.

Питоновский yield в ~2,5 раза медленнее, чем в CL в версии defun/cc и раза в полтора медленнее, чем питоновский же цикл, а то выше непонятно уточнил.

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

Но работают они как генераторы, если утка крякает как генератор ну и так далее. Из функции нельзя вызвать например как?

У тебя есть некий map по коллекции (mapc, maptree, maphash, do-all-symbols). Надо сделать итератор не копируя в этот итератор всю коллекцию в список. То есть

(defun map-to-iter (map)
  (lambda (c) (funcall map (lambda (x) (yield x)) c)))
monk ★★★★★
()
Ответ на: комментарий от alienclaster

А какая там сложность алгоритма? И почему именно от «времени выполнения» зависят тормоза?

Там CPS. Сделай macroexpand на (defun/cc …), поймёшь, почему это так медленно. А тормозит, потому что каждая вложенная функция добавляет контекст, учитывающий все предыдущие.

А в ракете вообще 1 к 1 скорость?

defun против defun/cc, определённо 1 к 1, так как выделенного defun/cc нет (в любой функции можно получать продолжения). А yield против for сегодня чуть позже померяю.

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

Да, так я ж написал - для оптимизаций.

При нормальной проверке типов малоосмысленно. Либо компилятор и так видит ограничение, либо придётся добавлять проверку (после которой компилятор опять же и так увидит ограничение). Проще явно писать unsafe-fx+, если уверен, что аргумент fixnum.

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

for tooks 12.230478399999999 seconds yield tooks 18.876121 seconds

Я провёл тесты и опечален.

В тесте заменил y.append(k) на y=0 ... y += k, чтобы минимизировать тяжёлые операции. Результаты практически не изменились.

$ python3 test.py
for tooks 38.175028483 seconds
yield tooks 50.29791783099999 seconds

А вот с Racket грустно. Со стандартной библиотекой:

~$ /usr/racket/bin/racket test.rkt
for
cpu time: 803 real time: 813 gc time: 0
yield
cpu time: 280953 real time: 286933 gc time: 1847

То есть без генератора в 50 раз быстрее, а с генератором в 6 раз медленнее.

С велосипедной версией генератора с https://stackoverflow.com/questions/44514890/does-call-cc-in-scheme-the-same-thing-with-yield-in-python-and-javascript

$ /usr/racket/bin/racket test2.rkt
for
cpu time: 844 real time: 854 gc time: 0
yield
cpu time: 66769 real time: 68290 gc time: 854

Тут всего лишь на 20% медленнее генераторной версии питона.

Выводы:

  1. В питоне в range(…) и так используется генератор, поэтому сильнее не тормозит. Генераторы являются базовыми конструкциями, поэтому работают чуть лучше, чем ручные.

  2. В ракете что-то не так со стандартной библиотекой. Но даже без неё использование генератора на продолжениях тормозит выполнение примерно в 80 раз.

P.S.

t1 = time.process_time()
i = 0
while i < 10000:
    i += 1
    y = 0
    k = 0
    while k < 10000:
        k += 1
        y += k
t2 = time.process_time()
print('while tooks {} seconds'.format(t2-t1))

$ python3 test2.py
while tooks 56.028962693 seconds
yield tooks 45.322027174 seconds

Уже ничего не понимаю.

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

А вот с Racket грустно. Со стандартной библиотекой:

А можешь показать как в ракете выглядит цикл и генератор?

То есть без генератора в 50 раз быстрее

Для sbcl получилось в 25 раз быстрее чем python3 на моей машине. С итераторами CL в 2,5 раза быстрее чем python-yield. Чуть позже проверю на CCL.

Генераторы являются базовыми конструкциями, поэтому работают чуть лучше, чем ручные.

Да, в питоне list comprehension () построен на оптимизированных генераторах емнип, так же как [for...] быстрее работает чем for циклы.

while tooks 56.028962693 seconds

Весело. Видимо, for и генераторы оптимизированы на уровне байткода.

yield tooks 45.322027174 seconds

yield ты тоже через while сделал? Если да, то вообще странно. Если это предыдущая версия yield то +-5 секунд, хотя и много, но в целом «так бывает». Хотя, наверное, погрешность должна быть не более 5%.

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

А можешь показать как в ракете выглядит цикл и генератор?

#lang racket/base
(require racket/generator)

(define my-stop-value (gensym))

(define (list-yield n)
  (generator ()
    (for ([i (in-range n)]) (yield i))
    my-stop-value))

(displayln "for")
(time
 (for ([i (in-range 10000)])
   (define y 0)
   (for ([k (in-range 10000)])
     (set! y (+ y k)))))

(displayln "yield")
(time
 (for ([i (in-range 10000)])
   (define y 0)
   (for ([k (in-producer (list-yield 10000) my-stop-value)])
     (set! y (+ y k)))))

С итераторами CL

cl-snakes с

(defgenerator list-yield (n)
    (loop for i from 0 to (- n 1) do (yield i))

?

Если это предыдущая версия yield то +-5 секунд, хотя и много, но в целом «так бывает».

Предыдущая.

Хотя, наверное, погрешность должна быть не более 5%.

Другие процессы есть. Может с чем-то тяжёлым пересеклось.

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

С итераторами CL

cl-snakes с

Нет, cl-cont (defun/cc) -> быстрее в 2,5 раза. snakes почти в 2 раза медленнее, чем defun/cc, то есть все еще быстрее питоньих генераторов на ~20-30%, а racket самопальная версия как я понял, _медленнее_ питоньих генераторов на 20%. А из стандартной библиотеки в 50-80 раз медленнее. И как у них так получилось...

alienclaster ★★★
()
Последнее исправление: alienclaster (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.