All streams
Search
Write a publication
Pull to refresh
-10
@ady1981read⁠-⁠only

SRE-инженер

Send message
«Знаю все буквы, не смог назвать слово» (С)
Если Ротон — это квазичастица в веществе, то почему передача тепла от такой частице другому веществу — это не обычная диффузия?
Насколько я понял, эффект состоит в том, что в некоторых веществах при некоторых условиях Ротоны существуют и их колебания описываются стоящей волной. Это и есть второй звук.
Сравнивая опыт своей работы в научной среде и в IT среде прихожу к выводу, что успех в научной среде определяется простотой (там, где получается простые вещи описывать просто — там и будет успех) :)
Интересно, что в заголовке статьи заложен и ответ: еще никто, кто списывает, не стал умнее :).
Если для задачи достаточно использовать исключения, то, конечно, ни контекстный метод не нужен, ни монады :).
Для простых задач действительно достаточно -> и try/catch на верхнем уровне.
В нашем случае мы используем Optlike библиотеку, в которой exception = смерть процесса, поэтому обычный -> + try/catch нам не подходит.
Спасибо за код.
Тут есть пару моментов, один — мелкий и еще один — крупный.

1) Элементарные функции в моем примере — это обычные функции. В вашем примере это — инструментированные монадами функции. Оба варианта имеют свои преимущества и недостатки.

2) Преимущество контекстного метода программирования в полную раскрывается при тестировании и при реализации retry фичи для бизнес-действия. Для этого мы используем until-first-error функцию, которая возвращает последнее состояние контекста до появления ошибки:
until-first-error(fs, init_context) -> [:ok updated_context] | [:error [reason last_context]]
Для retry фичи достаточно научить элементарные действия использовать существующие данные в контексте и завернуть until-first-error в цикл по количеству повторов:

;; [:ok updated_ctx] | [:error [last_reason last_ctx]]
(defn with-retry [ctx_fn max_attempts ctx & [ on-result-fn ]]
  (loop [attempt     1
         last_reason nil
         ctx         ctx]
    (if (> attempt max_attempts)
      [:error [last_reason ctx]]
      (match (ctx_fn ctx on-result-fn)

             [:ok updated_ctx]
             [:ok updated_ctx]

             [:error [reason updated_ctx]]
             (recur  (inc attempt) reason updated_ctx)))))

Пример вызова:
(with-retry (partial util/until-first-error fs) 10 {})

В случае с монадами вам нужно еще немного инструментировать элементарные действия?
Этих двух сущностей: элементарное действие и контекст, достаточно, чтобы эффективно реализовать бОльшую часть бизнес логики.
Те, кому нравятся монады — используют монады.
Те, кому не нравятся монады, могут использовать эту простую методологию.
Допустим, что у меня есть список функций — элементарных действий и начальный контекст. Можете дать ссылку на библиотеку с реализацией монады StateT Either / State+Either / what ever, чтобы завести мою балалайку (в которую я мог бы передать этот список функций и начальный контекст)?
>Допустим, вы берете лок и потом кидаете exception (в одном элементарном действии). Вопрос: откуда вы узнаете, что был взят лок?

Элементарное действие для лока будет иметь вид:
(defn get-lock [{:keys [value_a ...] :as context}]
(util/with-result-or-error
#(lock-service/create-lock value_a ... )
:lock
context))

Если exception возник в lock-service/create-lock, то значит лок не был создан и в context ничего сохранять не нужно, а если lock-service/create-lock вернул успешный результат, то он сохранится в контекст (первый же тест это покажет и если он успешный, то сохранение лока в контекст всегда будет успешным).
Через контекст эти задачи можно решать достаточно гибко, в зависимости от особенностей задачи.

>Ни что это за функция, ни какой был exception — вы не узнаёте.

Цель функции on-result-fn — обслужить завершение контекста, для логики, которая не зависит от результата. Например, если в элементарных действиях делается lock на ресурс, то ее реквизиты нужно сохранить в контексте и в функции on-result-fn этот lock нужно освободить.
При этом ничто не мешает определить успешность или неуспешность выполненных действий по содержимому контента.

>Более того, вы даже не знаете, on-result-fn вызывается после успешного выполнения или из-за ошибки, если не впихнёте это в context.

Если для какой-то логики для завершения контекста результат важен, то ее нужно добавлять после получения результата (результирующий контекст или описание ошибки).

>А вам что делать? У вас нет «продолжить отсюда». Вы можете только стартовать все операции с начала, и пропускать внутри те, которые уже были выполнены — проверяя внутри это контест. Так и я могу.

А как вы сможете это сделать только через комбинирование действий, если у вас нет начального контекста?
>А потом, когда вы хотите скомбинировать несколько «действий» в одно, которое бы вернуло итоговый результат или первую ошибку — вы можете написать комбинирующую функцию вручную, а можете воспользоваться тем, что Either это монада и значит её можно комбинировать с другими Either — как раз так, как вам надо. И о монадах можете продолжать не знать ничего, кроме как что они комбинируются между собой.

Ага, тут чёрт в деталях. А деталей тут как минимум две:
1) Что вернет ваша комбинирующая функция, в случае exception? И что вам делать, если внутри действий вы начинаете некоторую транзакцию?
Насколько я понимаю, этот exception просто убежит за пределы этой комбинирующей функции и в этот момент мы потеряете значение контекста, который был до exception. Чтобы вам сделать rollback в этом случае, вам как раз придется придумывать велосипеды (обертывать ваши действия в логику, которая будет сохранять контекст в кастомном exception и доставать этот контекст в catch).
В нашей реализации есть опциональная параметр-функция `on-result-fn` для `until-first-error`, в которой вы можете сделать rollback без всякого оверхеда.
2) А что вам делать, когда вы захотите сделать retry для этой операции (причем не для всех операций, а только для неуспешной)? Поскольку у вас нет контекста, а есть просто комбинирование, то вам придется делать для этого очередной велосипед.
Новые инструменты есть смысл применять для решения конкретных проблем/сложностей.
Мы решали проблему декомпозиции кода, чтобы не было длинных методов (и проблем, которые с этим связаны). И метод контекстного программирования эту проблему полностью решает.

>И как раз-таки ваше «элементарное действие» это просто функция, принимающая дополнительным параметром Context и возвращающая Either Error Context.

Элементарное действие принимает контекст единственным параметром. И как правило, это не просто какая-то функция, а функция в который вызывается бизнес функция (как правило, это запрос к бизнес-сервису), результат которой сохраняется в текущий контекст. Цель элементарного действия: сделать бизнес вызов и обслужить контекст, с результатом этого вызова.

>А что если мы хотим вернуть какой-то осмысленный результат? Конечно, вы можете возвращать пару из Context и результата.

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

Является ли контекстное программирование заменой монадного программирование и решает ли он те же задачи, что и монады?
Конечно, не является и не решает. Я думаю, что мы даже и не ставили те же задачи, для которых были разработаны монады :).
Сравнить контекстное программирование с программированием с монадами — действительно интересный и важный вопрос.
Любому человеку, незнакомому с контекстным программированием и монадным программированием из того, что вы написали понятно следующее:
— в контекстном программировании вводится понятие результата элементарного действия [:ok updated_context] | [:error reason] и, если это соответствует задаче, предлагается разбить логику на элементарные действия и использовать функцию `until-first-error`;
— в монадном программировании вводятся сущности: монада, типы монад (Maybe, Either, State, Reader, Writer и др.). И дальше предлагается изучить как это все готовить.
Возможно, что эта сложность монад, по мере роста масштаба приложения, в некоторый момент себя оправдает. Но совершенно очевидно, что для приложений среднего масштаба это сложность является излишней.
Ваш пример с Reader+State я пока не понял (в чем там killing feature у монад перед контекстным программированием), но еще подумаю.
В данном случае функции `with-lot-fn` и `buy-lot-fn` можно делать как приватными внутри функции `buy-lot` (в этом случае им доступны параметры функции `buy-lot` без прокидывания их через контекст), так и вынести их наружу (и тогда все данные нужно брать из контекста). Плюс, здесь нужно учитывать, что если функцию для каждого шага прописывать в неймспейсе, то у вас в этом неймспейсе получится адское количество таких функций, большинство которых будут использоватся только в одном месте.
Все остальное — дело вкуса, цвета и запаха :).
Что именно в программировании с монадами будет проще/лучше, чем в контекстном программировании?
Наверное, не все, что существует нужно применять потому, что это существует => хотелось бы узнать, чем программировании с монадами проще/лучше :)
Что именно заставляет «сердце кровоточить», интересно? :)
Статья безусловно актуальна для 1994 году, но в 2018 году хотелось бы узнать о конкретных инструментах, как создавать и проверять смарт-контракты :).
Мы используем плагин Cursive для IDEA: cursive-ide.com
>Но не понял как вы увязали параллельные запросы и модули.
Стейт модуля — это часть модуля. Так или иначе, этот стейт нужно шарить между потоками в многопоточном приложении.
Атомы решают проблему одновременного доступа к стейту, но с учетом наличия жизненного цикла этого стейта и зависимостей между модуляли такой стейт становится сложно поддерживать.
Но я не все проблемы назвал, на самом деле :).
Далее, в приложении появляется необходимость иметь линки между стейтами и иметь событийную систему.
Через атомы (+ сore.async) это тоже можно решить видимо, но решения проще чем в Erlang/OTP я пока не видел :).
Конкретно тот код — боевой, мы его используем в продакшене :).
Для js можете заходить на ClojureScript ;)
> стараемся чтобы он отвечал за обособленную часть логики
Опыт показывает, что уже для приложений среднего размера — это практически невозможно. В некоторый момент роста приложения появится зависимость между модулями.
Далее, как правило, появляются требования к серверу иметь стейты у модулей.
Далее, у стейтов появляются жизненные циклы. И надо не забыть, что модули уже зависят друг от друга.
И добавим к этому очевидное желание, чтобы сервер обрабатывал запросы параллельно.
Erlang и Otplike дают простое решения для этой гремучей смеси :).

Information

Rating
Does not participate
Registered
Activity