clojure-russia 2019-01-08

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

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

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

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

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

Прощаю 😁

подтвеждаю

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

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

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

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

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

ок, какие?

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

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

ладно

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

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

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

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

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

Failjure

это either

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

Где?

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

if test left right

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

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

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

Не

Нет right

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

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

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

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

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

А что важно?

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

Контекст

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

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

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

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

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

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

контекста

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

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

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

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

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

Да кто знает

Понаписуют

1

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

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?

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

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

👍🏻 1

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

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

Rich Hickey Maybe

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

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

Youtube rich hickey maybe not

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

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

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

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

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

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

я так и делал

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

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

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

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

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

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

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

а это объект

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

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

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

булевы тоже

или я путаю?

(defprotocol Foo
  (foo [x]))

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

(foo 1)
(foo false)

работает

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

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

это да

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

при помощи flow

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

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

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

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

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

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

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

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

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

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

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

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

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

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

во flow можно

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

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

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

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

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

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

да, верно

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

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

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

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

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

(else #(throw %))

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

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

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

там бенчмарк

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

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

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

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

но он как яд

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

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

пайплайна

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

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

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

совсем

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

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

можно flet

или в 1 мапу

ага, flet нашел

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

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

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

или log/error

красивее

читаемее

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

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

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

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

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

та можно

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

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

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

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

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

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

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

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

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

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

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

нет, throwable это throwable

он "типа left"

ну вот)

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

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

а Left - это left

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

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

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

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

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

давай)

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

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