clojure-russia

Работа и релокейт: #jobs-rus | #clojure-russia-offtop Телеграм-чат https://t.me/clojure_ru
kuzmin_m 2019-01-08T11:17:49.220100Z

Привет. Есть исключительные ситуации, а есть ожидаемые. Под исключинтельными ситуациями я понимаю: закончилась память, закончился диск, разорвалось соединение и т.п. И это реализуют с помощью исключений. А есть вполне ожидаемые прерывания основного потока выполнения. Пользователь ввел невалидные данные. Пользователь не имеет прав на операцию. Обычно советуют исползовать either, maybe и т.п. для этого случая. На первый взгляд отличие исключения от either в том, что исключение дороже из-за стек трейса. В для either мы должны особым образом структурировать код. А результат один - всплывание ошибок. Только в одном случае это делает jvm, а в другом мы сами. Так может не мучаться и использовать исключения без стектрейсов? Что думаете? Есть какие-то практические примеры?

fmnoise 2019-01-09T11:55:55.263400Z

flow сделан для того, чтобы использовать exception вместо either/left

fmnoise 2019-01-09T11:56:19.263600Z

сам either чужероден для кложуры как по мне

fmnoise 2019-01-09T11:56:49.263800Z

слишком много упаковки и распаковки

fmnoise 2019-01-09T11:57:58.264Z

в идеологии flow есть нормальные значения, а есть exception instances которые сингализируют об ошибке

fmnoise 2019-01-09T12:01:03.264200Z

насчет дороговизны трейсов, есть возможность использовать кастомный контейнер вместо ExceptionInfo, я сейчас думаю над этим. Хотел добавить поддержку "no stacktrace" в ex-info в core, но зареджектили, что вобщем-то ожидаемо

fmnoise 2019-01-09T17:47:30.264400Z

@kuzmin_m ^^

kuzmin_m 2019-01-09T18:25:52.264700Z

Если я правильно понял, то в flow используются идеи continuation-passing style (CPS). А исключения можно реализовать через континуации. И это равномощные вещи. Только исключения в jvm из коробки есть. Я пока не пробовал использовать исключения для ожидаемых ответвлений оснвного потока вычислений. Но пока единственная “проблема” - медленные исключения. Ну один случай из 10 будет чуть медленнее, если пользователь ошибется и введет что-то не то. И видимо из-за этого Миллер в в тикете отписался, что не видит проблем. Плюс, если кто-то добавил новый тип исключения, и забыли добавить его обработку, то тут стек-трейс полезен. Я где-то с полгода назад бенчмарки делал, и уже не помню, но исключения вроде раз в 100 или в 1000 были медленнее. Но это миллесекунды и наносекунды. Но зато нет накладных расходов в основном потоке исполнения. Я сравнивал исключения, better-cond, вложенные if и свою первую реализацию either. @fmnoise

fmnoise 2019-01-09T18:32:27.264900Z

не уверен насчет cps, там все навороченнее ментально чем во flow

kuzmin_m 2019-01-09T18:33:58.265100Z

ну там тоже коллбек передается

fmnoise 2019-01-09T18:34:11.265300Z

flow просто определяет инстанс исключения как fail, и дает 2 хелпера базирующихся на этом утверждении

fmnoise 2019-01-09T18:34:23.265500Z

так это не колбеки

fmnoise 2019-01-09T18:34:44.265700Z

просто классический threading macro

kuzmin_m 2019-01-09T18:34:49.265900Z

https://github.com/fmnoise/flow

kuzmin_m 2019-01-09T18:34:55.266200Z

мы же об этом говорим?

fmnoise 2019-01-09T18:34:56.266400Z

и try/catch под капотом

fmnoise 2019-01-09T18:34:58.266600Z

да

fmnoise 2019-01-09T18:35:12.266800Z

посмотри код, его там очень мало

kuzmin_m 2019-01-09T18:35:17.267Z

(defn check-login [req next]
  (if (:user req)
    next
    {:error "Login required"  :code 401}))

kuzmin_m 2019-01-09T18:35:27.267200Z

а next - не коллбэк?)

kuzmin_m 2019-01-09T18:35:46.267400Z

а

fmnoise 2019-01-09T18:35:49.267600Z

так это пример кода, от которого мы уходим)

kuzmin_m 2019-01-09T18:35:50.267800Z

он не вызывается

fmnoise 2019-01-09T18:35:58.268Z

при помощи flow

fmnoise 2019-01-09T18:37:08.268200Z

первый пример лесенка if if if if

fmnoise 2019-01-09T18:37:21.268400Z

которая потом рефакторится в колбеки

kuzmin_m 2019-01-09T18:37:24.268600Z

я значит не так понял

fmnoise 2019-01-09T18:37:39.268800Z

и типа ой все равно плохо

fmnoise 2019-01-09T18:37:46.269Z

давайте сделаем флоу

fmnoise 2019-01-09T18:38:05.269200Z

и на флоу все сразу хорошо)

kuzmin_m 2019-01-09T18:40:01.269400Z

я правильно понял, что это похоже на промисы в js?

fmnoise 2019-01-09T18:40:22.269600Z

не, это больше на монаду похоже

fmnoise 2019-01-09T18:40:30.269800Z

either

kuzmin_m 2019-01-09T18:40:38.270Z

так и проимсы - монада

fmnoise 2019-01-09T18:40:38.270200Z

и fmap

fmnoise 2019-01-09T18:41:10.270400Z

ну можно сказать и промис

fmnoise 2019-01-09T18:41:31.270600Z

просто в промисе еще асинхронность и тп

fmnoise 2019-01-09T18:41:53.270800Z

а тут просто try/catch и if

kuzmin_m 2019-01-09T18:42:03.271Z

там же тоже цепочка Promise(…).then().then().catch()

fmnoise 2019-01-09T18:42:29.271200Z

я не уверен можно ли в промисе вернуться на happy path

fmnoise 2019-01-09T18:42:40.271400Z

во flow можно

kuzmin_m 2019-01-09T18:42:59.271600Z

хм

fmnoise 2019-01-09T18:43:18.271800Z

в else можно вернуть значение

fmnoise 2019-01-09T18:43:35.272Z

и дальше опять будет then например

kuzmin_m 2019-01-09T18:44:42.272200Z

а как по функциям разбить?

fmnoise 2019-01-09T18:45:18.272400Z

(->> (call load-settings)
        (else (constantly default-settings))
        (then...)) 

fmnoise 2019-01-09T18:45:58.272700Z

а там все аргументы функции

kuzmin_m 2019-01-09T18:46:10.272900Z

а зачем ex-info? как правило исключение не выбрасывается же, а просто передается

fmnoise 2019-01-09T18:46:26.273100Z

да, верно

fmnoise 2019-01-09T18:46:38.273300Z

ex-info умеет мапу хранить

kuzmin_m 2019-01-09T18:46:49.273500Z

и при создании еще трейс вычисляет

fmnoise 2019-01-09T18:46:50.273700Z

с произвольными данными

kuzmin_m 2019-01-09T18:47:04.273900Z

так свой тип можно сделать

fmnoise 2019-01-09T18:47:07.274100Z

и никто не мешает тебе сделать throw

fmnoise 2019-01-09T18:47:28.274300Z

(else #(throw %))

fmnoise 2019-01-09T18:47:43.274500Z

трасса да, затратная

fmnoise 2019-01-09T18:48:05.274700Z

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

fmnoise 2019-01-09T18:48:21.274900Z

там посмотри открытое issue

fmnoise 2019-01-09T18:48:26.275100Z

там бенчмарк

fmnoise 2019-01-09T18:48:43.275300Z

без трассы сильно быстрее

kuzmin_m 2019-01-09T18:50:51.275500Z

Для понимания, я сам either делал https://github.com/darkleaf/either Но тогда не до конца разобрался зачем все это нужно. И сейчас кажется - что обычные исключения это подходящее решение. И как-то не убедительно для меня использование either-like подходов. Может быть я о каких-то случаях не знаю. Попробую свой either выпилить, может быть и найду такой случай.

fmnoise 2019-01-09T18:51:37.275800Z

свой тип да, сделал уже, щас думаю над апи чтобы выбирать надо тебе трасса или нет

fmnoise 2019-01-09T18:52:20.276Z

на either красиво пайплайны пишутся

fmnoise 2019-01-09T18:52:38.276200Z

но он как яд

kuzmin_m 2019-01-09T18:52:39.276400Z

если они нужны

fmnoise 2019-01-09T18:52:49.276600Z

нужны

kuzmin_m 2019-01-09T18:53:01.276800Z

а можешь пример привести?

kuzmin_m 2019-01-09T18:53:05.277Z

пайплайна

fmnoise 2019-01-09T18:54:10.277200Z

(defn remove-calendar-parent!
  [{:keys [user db conn admin?] :as ctx} {:keys [spaceId]} _]
  (->> (ensure-login user)
       (then (fn [_] (find-space db spaceId)))
       (then #(check-edit-ability % user admin?))
       (then #(perform-remove-calendar-parent conn %))
       (else respond-with-error)))

fmnoise 2019-01-09T18:54:17.277400Z

из боевого кода

fmnoise 2019-01-09T18:54:40.277600Z

ну это маленький

fmnoise 2019-01-09T18:54:42.277800Z

совсем

kuzmin_m 2019-01-09T18:56:39.278Z

а если у тебя зависимости появляются?

kuzmin_m 2019-01-09T18:56:45.278200Z

все в одну мапу писать?

fmnoise 2019-01-09T18:56:55.278400Z

можно flet

fmnoise 2019-01-09T18:57:23.278600Z

или в 1 мапу

kuzmin_m 2019-01-09T18:57:35.278800Z

ага, flet нашел

fmnoise 2019-01-09T18:57:44.279Z

мы часто в пайплайне датомик транзакцию собираем

fmnoise 2019-01-09T18:58:10.279200Z

а потом делаем transact

kuzmin_m 2019-01-09T18:58:16.279400Z

а чем это лучше try/catch?

fmnoise 2019-01-09T18:58:20.279600Z

или log/error

fmnoise 2019-01-09T18:58:36.279800Z

красивее

fmnoise 2019-01-09T18:58:42.280Z

читаемее

fmnoise 2019-01-09T18:59:13.280200Z

особенно когда начинаются вложенные try/catch

kuzmin_m 2019-01-09T18:59:34.280400Z

так, а зачем тут вложенные try/catch?

kuzmin_m 2019-01-09T18:59:57.280600Z

flow спасет от вложенных т.к. умеет вернуть в нормальную ветку

kuzmin_m 2019-01-09T19:00:25.280800Z

на самом верху объявил try/catch и лови все что нужно

kuzmin_m 2019-01-09T19:01:39.281Z

да даже тот же вложенный try/catch можно в специализированную фукнцию завернуть, что бы вычисление в номальную ветку возвращалось

fmnoise 2019-01-09T19:02:41.281200Z

та можно

fmnoise 2019-01-09T19:02:56.281400Z

но мне не нравится try/catch

fmnoise 2019-01-09T19:03:05.281600Z

семантически

fmnoise 2019-01-09T19:03:38.281800Z

тогда надо делать throw

fmnoise 2019-01-09T19:03:48.282Z

а throw это сайд эффект

fmnoise 2019-01-09T19:04:38.282200Z

а так мы меняем throw на возврат инстанса исключения

kuzmin_m 2019-01-09T19:04:48.282400Z

т.е. мы перешли в область нравится/не нравится и измеримых факторов, кроме скорости не осталось это не наезд, просто констатация у всех есть вкусы и это нормально

fmnoise 2019-01-09T19:04:49.282600Z

и получается красиво

fmnoise 2019-01-09T19:05:21.282800Z

не, я не говорю что flow что-то решает кроме читабельности

fmnoise 2019-01-09T19:05:46.283Z

не вводя при этом новых абстракций типа right/left

kuzmin_m 2019-01-09T19:06:31.283200Z

так throwable у тебя и есть left)

kuzmin_m 2019-01-09T19:06:38.283400Z

или я не так понял?

fmnoise 2019-01-09T19:06:51.283600Z

нет, throwable это throwable

fmnoise 2019-01-09T19:07:04.283800Z

он "типа left"

kuzmin_m 2019-01-09T19:07:26.284100Z

ну вот)

fmnoise 2019-01-09T19:07:38.284300Z

если ты сравниваешь с either

kuzmin_m 2019-01-09T19:07:41.284500Z

а в мойе реализации Object - типа Right

kuzmin_m 2019-01-09T19:07:51.284700Z

а Left - это left

kuzmin_m 2019-01-09T19:08:21.285Z

ладно, понятно более-менее

fmnoise 2019-01-09T19:08:44.285200Z

но throwable был есть и будет

fmnoise 2019-01-09T19:08:44.285400Z

это не новая абстракция

fmnoise 2019-01-09T19:08:45.285600Z

а left/right это хаскель

kuzmin_m 2019-01-09T19:12:10.285800Z

давай дальше уже в личку

fmnoise 2019-01-09T19:12:19.286Z

давай)

kuzmin_m 2019-01-09T19:12:29.286200Z

только ты не отвечаешь

kuzmin_m 2019-01-09T19:12:33.286400Z

а вот

a.dan 2019-01-10T16:30:49.295500Z

гады 🙂 Зажали размышления

a.dan 2019-01-10T16:31:22.295700Z

Такая любопытная тема, а они в личку

kuzmin_m 2019-01-10T16:31:58.295900Z

да мы в личке datomic обсуждали

kuzmin_m 2019-01-10T16:32:09.296100Z

про исключения тут все

a.dan 2019-01-10T16:32:28.296300Z

Прощаю 😁

fmnoise 2019-01-10T16:45:00.296500Z

подтвеждаю

2019-01-08T11:31:18.221Z

https://dev.clojure.org/jira/browse/CLJ-2423 Miller против 🤷

kirill.salykin 2019-01-08T11:33:10.221800Z

> Обычно советуют исползовать either, maybe и т.п. для этого случая. мне кажется формулировка “обычно советуют” - ложна. Кто, где, почему? > На первый взгляд отличие исключения от either в том, что исключение дороже из-за стек трейса. дороговизна понятие относильное, но основное отличие - контекст исключение может быть поймано где выше в асбтракции и что с ним делать? тот же go to - не просто же так считается плохим паттерном

kuzmin_m 2019-01-08T11:34:15.222100Z

По поводу стека - это оптимизация, и к вопросу слабо относится.

kirill.salykin 2019-01-08T11:35:19.222300Z

и почему в итого either vs exception?

kirill.salykin 2019-01-08T11:35:26.222500Z

других вариантов нет?

kuzmin_m 2019-01-08T11:35:33.222700Z

ок, какие?

kirill.salykin 2019-01-08T11:35:49.222900Z

вернуть ошибку

kuzmin_m 2019-01-08T11:36:02.223100Z

и чем это поможет?

kirill.salykin 2019-01-08T11:36:11.223300Z

ладно

kirill.salykin 2019-01-08T11:36:19.223500Z

какую проблему вы хотите решить

kuzmin_m 2019-01-08T11:37:14.223700Z

Есть некий сценарий. что-то получить на вход проверить сделать операцию проверить контекст сделать операцию проверить результат что-то сделать

kuzmin_m 2019-01-08T11:37:44.224Z

если проверка не прошла дальше делать нет смысла

kuzmin_m 2019-01-08T11:37:54.224200Z

и нужно вернуться на верх

kuzmin_m 2019-01-08T11:39:09.224400Z

т.е. подходит и exception и either но вроде как exception для этого не стоит использовать а either - нужно код особым образом писать

kirill.salykin 2019-01-08T11:39:24.224700Z

Failjure

kirill.salykin 2019-01-08T11:39:39.225Z

https://github.com/adambard/failjure

2019-01-08T11:39:46.225300Z

https://github.com/fmnoise/flow

kuzmin_m 2019-01-08T11:39:55.225600Z

это either

kuzmin_m 2019-01-08T11:40:06.225900Z

мне нужно особым обрзом код писать

kirill.salykin 2019-01-08T11:40:07.226100Z

Где?

2019-01-08T11:40:15.226300Z

flow

kuzmin_m 2019-01-08T11:40:20.226500Z

(if (empty? s)
    (f/fail "Please enter a value")
    s))

kuzmin_m 2019-01-08T11:40:31.226800Z

if test left right

kuzmin_m 2019-01-08T11:41:21.227Z

все библиотеки заставляют особым образом код структурировать, чтобы можно было ошибку на верх передать я не очень понимаю зачем, если есть исключения

kuzmin_m 2019-01-08T11:41:30.227200Z

которы именно это и делают

kuzmin_m 2019-01-08T11:42:45.227400Z

может быть я что-то не так понимаю?

kirill.salykin 2019-01-08T11:53:11.227700Z

Не

kirill.salykin 2019-01-08T11:53:23.228200Z

Нет right

kuzmin_m 2019-01-08T11:53:50.228400Z

видимо, там right - это все, что не left

kuzmin_m 2019-01-08T11:54:06.228800Z

т.е. любой объект по дефолту - right

kirill.salykin 2019-01-08T11:54:12.229200Z

Тогда if - это монады )

kirill.salykin 2019-01-08T11:54:19.229600Z

С такой логикой

kuzmin_m 2019-01-08T11:54:38.230100Z

но это вообще не важно

kirill.salykin 2019-01-08T11:54:51.230500Z

А что важно?

kuzmin_m 2019-01-08T11:54:58.230900Z

чем either и компания лучше исключений?

kirill.salykin 2019-01-08T11:55:14.231400Z

Контекст

kuzmin_m 2019-01-08T11:55:21.231600Z

что за контекст?

kirill.salykin 2019-01-08T11:55:42.232300Z

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

kirill.salykin 2019-01-08T11:55:53.232700Z

Без доступа к контексту

kuzmin_m 2019-01-08T11:56:02.232900Z

что за контекст то?

kirill.salykin 2019-01-08T11:56:33.233200Z

https://ru.m.wikipedia.org/wiki/Контекст

kuzmin_m 2019-01-08T11:57:08.233500Z

адмирал ясен ... просто 😃

kuzmin_m 2019-01-08T11:57:25.234100Z

может пример какой-то есть?

kuzmin_m 2019-01-08T11:57:31.234400Z

контекста

kirill.salykin 2019-01-08T11:57:37.234800Z

В общем обсуждение довольно деструктивное

kirill.salykin 2019-01-08T11:57:51.235400Z

Какие проблемы решаются - я не понимаю

kirill.salykin 2019-01-08T11:57:59.235800Z

Так что врядли могу что подсказать

kirill.salykin 2019-01-08T11:58:11.236400Z

Если исключения работают - используйте

kuzmin_m 2019-01-08T11:58:50.236600Z

зачем тогда все эти flow, failjure?

kirill.salykin 2019-01-08T11:59:04.237Z

Да кто знает

kirill.salykin 2019-01-08T11:59:08.237300Z

Понаписуют

1
kirill.salykin 2019-01-08T11:59:16.237700Z

Потом ломай голову

kuzmin_m 2019-01-08T12:04:01.237900Z

Everyone here is repeating the phrase “exceptions are for exceptional circumstances”, but that really doesn’t give any understanding of why its bad to use them for unexceptional circumstances. I need more than that. Is the performance hit of throwing exceptions really that bad? Are there any benchmarks available?

misha 2019-01-08T12:21:04.239100Z

Рич норм вброс про изер и мейби слелал на конже.

misha 2019-01-08T12:24:21.244Z

На предыдущем проекте у нас было вида 3 разных имплементаций монад. Всё такой ад, даже с тредин-макросами, которые сами заворачивают/разворачивают конвертики эти. Самый ништяк - эксепшены с ex-data, и потом диспатчить в трай по типу эксепшена (какой-нибудь :error/type в ex-info)

1
kuzmin_m 2019-01-08T12:25:39.244200Z

@misha а есть ссылка на вброс?

kuzmin_m 2019-01-08T12:25:47.244400Z

или пара ключевиков?

kirill.salykin 2019-01-08T12:26:07.244700Z

Rich Hickey Maybe

kirill.salykin 2019-01-08T12:26:14.245100Z

опять побуду капитаном 😉

kuzmin_m 2019-01-08T12:26:18.245300Z

т.е. пока исключения проигрывают только по производительности

misha 2019-01-08T12:26:21.245600Z

Youtube rich hickey maybe not

kuzmin_m 2019-01-08T12:26:38.245900Z

https://www.youtube.com/watch?v=YR5WdGrpoug

kirill.salykin 2019-01-08T12:26:44.246300Z

но в том толке maybe расматривается в другом контексте

misha 2019-01-08T12:27:29.247900Z

Там реально надо каждую функцию или лифтить или рефакторить, в итоге запутываешься в бюрократии на ровном месте, где надо и где не надо

misha 2019-01-08T12:27:46.248200Z

Ну это всё равно цена

kuzmin_m 2019-01-08T12:27:51.248600Z

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

misha 2019-01-08T12:28:20.249400Z

Типа того, послушай, там первых минут 10-15

kuzmin_m 2019-01-08T12:28:32.249600Z

ну в clojure можно Object сделать Righht

kuzmin_m 2019-01-08T12:28:38.249800Z

я так и делал

kuzmin_m 2019-01-08T12:28:56.250Z

т.е. это проблема в хаскеле

kuzmin_m 2019-01-08T12:29:04.250200Z

вернее даже в реализации

misha 2019-01-08T12:29:25.250800Z

Ну а примитивы как? Заворачивать?

misha 2019-01-08T12:30:17.252100Z

Да тебе потом в любом случае на клиенте проверять «лефт или обж-экстендс-райт»

kuzmin_m 2019-01-08T12:30:38.252600Z

> Ну а примитивы как? Заворачивать?

kuzmin_m 2019-01-08T12:30:45.252900Z

что за примитивы?

kuzmin_m 2019-01-08T12:31:00.253500Z

в clojure строка - это java.lang.String

kuzmin_m 2019-01-08T12:31:03.253800Z

а это объект

kuzmin_m 2019-01-08T12:31:16.254300Z

для nil отдельную реализацию

misha 2019-01-08T12:31:24.254800Z

С тз поддержки кодобазы - это был major gemor. И оно ж потом как рак метастазы во все уголки проекта пускает

misha 2019-01-08T12:31:36.255Z

7

misha 2019-01-08T12:31:42.255400Z

false

kuzmin_m 2019-01-08T12:32:05.255900Z

числа тоже объекты

kuzmin_m 2019-01-08T12:32:09.256100Z

булевы тоже

kuzmin_m 2019-01-08T12:32:17.256300Z

или я путаю?

kuzmin_m 2019-01-08T12:33:27.256800Z

(defprotocol Foo
  (foo [x]))

(extend-protocol Foo
  Object
  (foo [_] :obj))

(foo 1)
(foo false)

kuzmin_m 2019-01-08T12:33:30.257Z

работает

kuzmin_m 2019-01-08T12:34:06.258200Z

@misha а к чему в итоге то пришли? исключения?

misha 2019-01-08T12:34:11.258500Z

Не всё завернуто как минимум потому что тебе может из жавы примитив предыдущая ф-я вернуть после интеропа

kuzmin_m 2019-01-08T12:34:29.259Z

это да

misha 2019-01-08T12:35:34.260800Z

К тому, что времени выкашивать нет. Там до сих пор срач, где эксепшены, где одни монады, где катс, где мапы еррор/валью, где вектора лево/право

kuzmin_m 2019-01-08T15:48:46.263Z

Если кому-то интересны continuations - то можем обсудить. Я собрал jvm от проекта loom, который добавляет поддержку файберов. Вот маленький пример

(ns yield
  (:import
   [java.lang ContinuationScope Continuation]))

(let [scope (ContinuationScope. "example")
      f     (fn []
              (prn :fn-start)
              (Continuation/yield scope)
              (prn :fn-end))
      cont  (Continuation. scope (fn []
                                   (prn 1)
                                   (Continuation/yield scope)
                                   (f)))]
  (while (not (.isDone cont))
    (.run cont)
    (prn :control)))

;; 1
;; :control
;; :fn-start
;; :control
;; :fn-end
;; :control