Fork me on GitHub
#clojure-russia
<
2019-01-08
>
kuzmin_m11:01:49

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

kirill.salykin11:01:10

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

kuzmin_m11:01:15

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

kirill.salykin11:01:19

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

kirill.salykin11:01:26

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

kuzmin_m11:01:33

ок, какие?

kirill.salykin11:01:49

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

kuzmin_m11:01:02

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

kirill.salykin11:01:19

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

kuzmin_m11:01:14

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

kuzmin_m11:01:44

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

kuzmin_m11:01:54

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

kuzmin_m11:01:09

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

kuzmin_m11:01:55

это either

kuzmin_m11:01:06

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

kuzmin_m11:01:20

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

kuzmin_m11:01:31

if test left right

kuzmin_m11:01:21

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

kuzmin_m11:01:30

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

kuzmin_m11:01:45

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

kuzmin_m11:01:50

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

kuzmin_m11:01:06

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

kirill.salykin11:01:12

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

kirill.salykin11:01:19

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

kuzmin_m11:01:38

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

kirill.salykin11:01:51

А что важно?

kuzmin_m11:01:58

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

kirill.salykin11:01:14

Контекст

kuzmin_m11:01:21

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

kirill.salykin11:01:42

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

kirill.salykin11:01:53

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

kuzmin_m11:01:02

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

kuzmin_m11:01:08

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

kuzmin_m11:01:25

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

kuzmin_m11:01:31

контекста

kirill.salykin11:01:37

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

kirill.salykin11:01:51

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

kirill.salykin11:01:59

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

kirill.salykin11:01:11

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

kuzmin_m11:01:50

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

kirill.salykin11:01:04

Да кто знает

kirill.salykin11:01:08

Понаписуют

kappa 5
kirill.salykin11:01:16

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

kuzmin_m12:01:01

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?

misha12:01:04

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

misha12:01:21

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

5
kuzmin_m12:01:39

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

kuzmin_m12:01:47

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

kirill.salykin12:01:07

Rich Hickey Maybe

kirill.salykin12:01:14

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

kuzmin_m12:01:18

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

misha12:01:21

Youtube rich hickey maybe not

kirill.salykin12:01:44

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

misha12:01:29

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

misha12:01:46

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

kuzmin_m12:01:51

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

misha12:01:20

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

kuzmin_m12:01:32

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

kuzmin_m12:01:38

я так и делал

kuzmin_m12:01:56

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

kuzmin_m12:01:04

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

misha12:01:25

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

misha12:01:17

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

kuzmin_m12:01:38

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

kuzmin_m12:01:45

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

kuzmin_m12:01:00

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

kuzmin_m12:01:03

а это объект

kuzmin_m12:01:16

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

misha12:01:24

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

kuzmin_m12:01:05

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

kuzmin_m12:01:09

булевы тоже

kuzmin_m12:01:17

или я путаю?

kuzmin_m12:01:27

(defprotocol Foo
  (foo [x]))

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

(foo 1)
(foo false)

kuzmin_m12:01:30

работает

kuzmin_m12:01:06

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

misha12:01:11

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

kuzmin_m12:01:29

это да

misha12:01:34

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

fmnoise11:01:55

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

fmnoise11:01:19

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

fmnoise11:01:49

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

fmnoise11:01:58

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

fmnoise12:01:03

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

kuzmin_m18:01:52

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

fmnoise18:01:27

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

kuzmin_m18:01:58

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

fmnoise18:01:11

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

fmnoise18:01:23

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

fmnoise18:01:44

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

kuzmin_m18:01:55

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

fmnoise18:01:56

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

fmnoise18:01:12

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

kuzmin_m18:01:17

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

kuzmin_m18:01:27

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

fmnoise18:01:49

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

kuzmin_m18:01:50

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

fmnoise18:01:58

при помощи flow

fmnoise18:01:08

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

fmnoise18:01:21

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

kuzmin_m18:01:24

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

fmnoise18:01:39

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

fmnoise18:01:46

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

fmnoise18:01:05

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

kuzmin_m18:01:01

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

fmnoise18:01:22

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

kuzmin_m18:01:38

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

fmnoise18:01:10

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

fmnoise18:01:31

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

fmnoise18:01:53

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

kuzmin_m18:01:03

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

fmnoise18:01:29

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

fmnoise18:01:40

во flow можно

fmnoise18:01:18

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

fmnoise18:01:35

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

kuzmin_m18:01:42

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

fmnoise18:01:18

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

fmnoise18:01:58

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

kuzmin_m18:01:10

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

fmnoise18:01:26

да, верно

fmnoise18:01:38

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

kuzmin_m18:01:49

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

fmnoise18:01:50

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

kuzmin_m18:01:04

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

fmnoise18:01:07

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

fmnoise18:01:28

(else #(throw %))

fmnoise18:01:43

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

fmnoise18:01:05

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

fmnoise18:01:21

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

fmnoise18:01:26

там бенчмарк

fmnoise18:01:43

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

kuzmin_m18:01:51

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

fmnoise18:01:37

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

fmnoise18:01:20

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

fmnoise18:01:38

но он как яд

kuzmin_m18:01:39

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

fmnoise18:01:49

нужны

kuzmin_m18:01:01

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

kuzmin_m18:01:05

пайплайна

fmnoise18:01:10

(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)))

fmnoise18:01:17

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

fmnoise18:01:40

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

fmnoise18:01:42

совсем

kuzmin_m18:01:39

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

kuzmin_m18:01:45

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

fmnoise18:01:55

можно flet

fmnoise18:01:23

или в 1 мапу

kuzmin_m18:01:35

ага, flet нашел

fmnoise18:01:44

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

fmnoise18:01:10

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

kuzmin_m18:01:16

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

fmnoise18:01:20

или log/error

fmnoise18:01:36

красивее

fmnoise18:01:42

читаемее

fmnoise18:01:13

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

kuzmin_m18:01:34

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

kuzmin_m18:01:57

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

kuzmin_m19:01:25

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

kuzmin_m19:01:39

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

fmnoise19:01:41

та можно

fmnoise19:01:56

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

fmnoise19:01:05

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

fmnoise19:01:38

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

fmnoise19:01:48

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

fmnoise19:01:38

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

kuzmin_m19:01:48

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

fmnoise19:01:49

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

fmnoise19:01:21

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

fmnoise19:01:46

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

kuzmin_m19:01:31

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

kuzmin_m19:01:38

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

fmnoise19:01:51

нет, throwable это throwable

fmnoise19:01:04

он "типа left"

kuzmin_m19:01:26

ну вот)

fmnoise19:01:38

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

kuzmin_m19:01:41

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

kuzmin_m19:01:51

а Left - это left

kuzmin_m19:01:21

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

fmnoise19:01:44

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

fmnoise19:01:44

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

fmnoise19:01:45

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

kuzmin_m19:01:10

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

fmnoise19:01:19

давай)

kuzmin_m19:01:29

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

a.dan16:01:49

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

a.dan16:01:22

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

kuzmin_m16:01:58

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

kuzmin_m16:01:09

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

a.dan16:01:28

Прощаю 😁

fmnoise16:01:00

подтвеждаю

serioga18:01:31

ну, я пробовал делать ещё такое: для flow использовать обычный some-> по операциям, возвращающим nil сами операции можно оборачивать макросами, которые для nil (или при исключениях) аккумулируют результат во что-то соответстующее контексту получалось прикольно можно смешивать результат от операций, которые по разному работают (возвращают nil, бросают исключение) и самому определять форму аккумулятора «ошибки» в конкретном контексте

kuzmin_m15:01:46

Если кому-то интересны 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

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

ну, я пробовал делать ещё такое: для flow использовать обычный some-> по операциям, возвращающим nil сами операции можно оборачивать макросами, которые для nil (или при исключениях) аккумулируют результат во что-то соответстующее контексту получалось прикольно можно смешивать результат от операций, которые по разному работают (возвращают nil, бросают исключение) и самому определять форму аккумулятора «ошибки» в конкретном контексте