Fork me on GitHub
#clojure-russia
<
2017-08-30
>
kuzmin_m07:08:56

есть еще вот такое: https://github.com/Engelberg/better-cond/tree/v2.0.1

(b/defnc call [ctx params]
  :let [err (or
             (check-logged-out ctx)
             (check-params params)
             (check-registered ctx params))]
  (some? err) [nil err]
  :let [user-id (create-user ctx params)]
  :do  (sign-in ctx user-id)
  :let [res {:user-id user-id}]
  [res nil])

anjensan08:08:39

а какова мотивация? кода ведь не меньше, читается не лучше... чтобы людям, незнакомым с этой либой (better-cond), было сложнее понять исходник?

anjensan08:08:09

нет. я про пример выше

kuzmin_m08:08:45

код стал линейным

kuzmin_m08:08:18

если рассматривать только мой пример, то эта либа "избыточна"

kuzmin_m08:08:32

но если делать проект, то должен быть один подход

kuzmin_m08:08:55

и "ранний возврат" позволяет присать линейный код

anjensan08:08:46

т.е. позволяет мыслить императивно?

kuzmin_m08:08:18

ранний возврат - это императив?

anjensan09:08:26

нет. но со всякими :let и :do структура получается в стиле императивного кода (типо if (x) { return null; })

anjensan09:08:38

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

anjensan09:08:29

но... конечно такой болшой и сложный макрос -- это не настолько круто, как отдельная либа 🙂

anjensan09:08:33

@kuzmin_m, такое решене для написания "линейного" кода подойдет?

anjensan10:08:38

а где там поддержка "раннего возврата" ? 🙂

anjensan10:08:03

а вру-вру. там пары тест-значение

anjensan10:08:32

так мое решение не подходит?

kuzmin_m10:08:34

я не знаю) но направление правильное я не хочу разбираться сейчас в этом и ломать мозг рекурсией макросов в том же better-cond тоже рекурсивный макрос, но я его не вижу, он внутри библиотеки в better-cond оно протестировано и используется

kuzmin_m10:08:02

можно, например, посмотреть тесты в better-cond

kuzmin_m10:08:16

и посмотреть, как твоя раеализация с ними соотносится

kuzmin_m10:08:39

если better-cond действительно можно выкинуть и зменить на макрос в 2 строчки, то это круто

anjensan10:08:22

ок. поясню. этот мега-макрос после разворачивания дает примерно такое

anjensan10:08:21

а если не поленится, и правильно отформатировать, то получим

anjensan10:08:17

а если присмотреться, то можно и лишний do убрать

anjensan10:08:27

и получим как раз тот код, который я изначально предлагал 😃

anjensan10:08:56

поэтому я не вижу реального профита от такой записи -- ну мы не лесенкой пишем, а "линейно"... но для этого нам нужно "ломать мозг рекурсией макросов" (хотя макрос наитривиальнейший на самом то деле)

anjensan10:08:39

имхо гораздо полезнее таки подпривыкнуть к чтению "кода лесенкой"

anjensan10:08:27

благо тогда и новые фичи проще добавлять (доп ветки в if-ы проще будет вставлять, цикл можно будет запилить каокй и т.п.)

kuzmin_m10:08:07

спасибо за макрос подумаю

razum2um10:08:08

@kuzmin_m на самом деле лесенка меня тоже цепляет, но это всего лишь напоминание разбить ф-ю на части

(defn ctx-errors [ctx params]
  (or
    (check-logged-out ctx)
    (check-params params)
    (check-registered ctx params)))

(defn create-user-and-sign-in [ctx params] ;; TODO better name
   (let [user-id (create-user ctx params)]
     (sign-in ctx user-id)
     {:user-id user-id}))

(defn call [ctx params]
  (if-let [err (ctx-errors ctx params)]
    [err nil]
    [nil (create-user-and-sign-in ctx params)]))

kronos_vano10:08:05

(->> (create-user ctx params)
     (sign-in ctx)   
     (assoc {} :user-id))

razum2um10:08:41

ну я хз, что там sign-in возвращает, но я думаю, что новый контекст, а не юзера

kuzmin_m10:08:01

ничего он не возвращает, там побочный эффект

razum2um10:08:10

кстати да, вообще @kuzmin_m немного смущает, что sign-in это сайд-эффект. как насчет между флоу меняться контекстом и в изначальной ф-и это будет не сайд-эффект, а leaf нода в логике. тогда и тредмакросы будут красивы

razum2um10:08:38

и я тут тоже думал, что лучше юзать как аналог Either, и мне кажется лучше это хеш типа {:err nil :res nil} чем вектор.

razum2um10:08:35

потому что можно делать (-> {:err nil :res nil} fn-whoch-assocs-errors fn-which-shortcuts-on-error-via-macros-and-assocs-res)

kuzmin_m10:08:16

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

def move_link_to_packet_for_replaced_race(race)
    return if data.replaced_order_uid.blank?
    replaced_race = Race.find_by(uid: data.replaced_order_uid)
    return if replaced_race.blank?
    return if replaced_race.packet.blank?
    replaced_race.packet_attached_race.update! race: race
  end

razum2um10:08:03

придумай контекст для этого флоу, можно больше чем выше, а прям {:err nil :res nil :step1 nil :step2 nil} и тредмакросом его прогоняй по каждому шагу

kuzmin_m10:08:15

зачем?

kuzmin_m10:08:29

зачем мозг ломать, если есть let

kuzmin_m10:08:49

ну это как бы "императивный" подход

kuzmin_m11:08:00

это бизнес логика

anjensan11:08:03

как я понимаю идет переписывание с рубях?

kuzmin_m11:08:14

нет, это просто эксперимент

kuzmin_m11:08:32

и бизнес-логика как бы подразумевает императив

kuzmin_m11:08:38

сделай это сделай то

razum2um11:08:58

эм. мне кажется clojure-way это сделать типа такого

(defn pipeline []
 (macros-aka-some->-but-for-context-> 
   empty-pipeline-context
   (step1 ctx)
   (step2 params)))

anjensan11:08:01

не обязательно

kuzmin_m11:08:20

зачем мне слова заказчика в голове переводить в "функциональный" стиль через передачу контекста через -> ?

anjensan11:08:37

я бы не стал делать через контекст и threading если есть побочные эффекты

kuzmin_m11:08:39

а потом обратно с кода в слова заказчика

kuzmin_m11:08:59

понятно, что там, где это работает, это нужно применять

kuzmin_m11:08:15

какая-то работа с данными, там -> заходит на ура

razum2um11:08:29

так и сделай процесс работой с данными! 🙂

kuzmin_m11:08:41

к тебе пришел заказчик

kuzmin_m11:08:43

говорит

kuzmin_m11:08:52

хочу, что бы юзеры регались

kuzmin_m11:08:55

ты такой - ок

kuzmin_m11:08:00

а как это?

kuzmin_m11:08:14

ну вот нужно сначала юзера создать

kuzmin_m11:08:19

а потом залогинить

kuzmin_m11:08:31

а ты такой, ну наверно, нужно еще проверки сделать

kuzmin_m11:08:36

- а, ну точно

razum2um11:08:38

@anjensan кстати, хорошее замечание. я думаю идеально, если этот тредмакрос выдает агрументы и инструкции для вызова сайдэффектом. redux-saga типа. ну и теститься будет на ура

kuzmin_m11:08:05

что не залогинен что такого нет в базе что логин и пароль подходят под требования

kuzmin_m11:08:11

где тут работа с данными?

anjensan11:08:31

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

anjensan11:08:49

мол "айай. я учу clojure, но не хочу писать clojure-way код... хочу как на рубях"

anjensan11:08:45

"я не хочу писать (if (> a b) (inc a) (dec b)), я хочу "(if (> a b) (return (inc a)) (dec b)"

anjensan11:08:49

ну это, право, забавно

razum2um11:08:17

@anjensan у тебя какой бекграунд кстати?

anjensan11:08:49

бекграунд в плане? насколько я знаком clojure ?

razum2um11:08:33

ну до этого на чем писал? я тоже из рубей и мне тоже не хватало(ет?) раннего ретурна, но для меня это решается cond->/some-> & nil pinning или аналогом как выше. ну и более детальной разбивкой

kuzmin_m11:08:35

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

anjensan11:08:39

java -> python -> js -> java -> вот на go перехожу

kuzmin_m11:08:16

я так понимаю, что как в первом варианте принято писать в кложе

kuzmin_m11:08:24

это звездец

razum2um11:08:31

да Миш, я-то за, что лесенка печальна. не утрируй

anjensan11:08:51

нет. так не принято

kuzmin_m11:08:06

а как? контекст передавать?

kuzmin_m11:08:27

типа (-> {} (do1) (do2))

razum2um11:08:52

норм же.. сам же выше ссылался на either 🙂

delaguardo11:08:55

Через binding

kuzmin_m11:08:42

незнаю, но для меня пока выигрывает better-cond ну или макрос %%

anjensan11:08:43

не надо использовать такой макрос! это прямой путь начать играть в сарказмбол 😉

kuzmin_m11:08:10

either она про обработку ошибок

kuzmin_m11:08:16

а не про ранний возврат

anjensan11:08:18

вот чем такой код плох?

kuzmin_m11:08:31

т.е. или ранний возврат или обработка ошибок

kuzmin_m11:08:07

в середине нельзя значение объявить

kuzmin_m11:08:49

вот еще пример

kuzmin_m11:08:58

вчера ссылку эту присылал

kuzmin_m11:08:07

и да, ее Афир написал))

razum2um11:08:09

может “середина” означает, что процесса по факту 2? т.е. руби функцию тоже бы отрефакторить, как думаешь?

kuzmin_m11:08:31

def move_link_to_packet_for_replaced_race(race)
    return if data.replaced_order_uid.blank?
    replaced_race = Race.find_by(uid: data.replaced_order_uid)
    return if replaced_race.blank?
    return if replaced_race.packet.blank?
    replaced_race.packet_attached_race.update! race: race
  end

kuzmin_m11:08:33

отрефактори

kuzmin_m11:08:08

это нельзя сделать

anjensan11:08:39

слаб духом тот, кто с императивного языка на фп переписывать пробует 1-в-1 код свой

kuzmin_m11:08:55

давай на личности не будем переходить)

anjensan11:08:58

я не переходил на личности 😃

anjensan11:08:08

или... ты пытаешься 1-в-1 переписывать?

anjensan11:08:40

ну это реально больно и мучительно. даже с руби на какую джаву будет больно переписывать

anjensan11:08:47

а уж про clojure я молчу

razum2um11:08:05

@kuzmin_m что думаешь если так

def move_link_to_packet_for_replaced_race(race)
	return if data.replaced_order_uid.blank?
	RaceWrap.new(Race.find_by(uid: data.replaced_order_uid)).move
end

class RaceWrap # some process class wrapping race maybe extended by some functionality further
  attr_reader :race

  def initialize(race)
    @race = race
  end

  def can_packet_attached_race?
    race.present? && race.packet.present?
  end

  def update
    race.packet_attached_race.update! race: race
  end

  def move
    update if can_packet_attached_race?
  end
end
конечно “плодить сущности” плохо и правильно назвать враппер надо, но по мне в инзачальной краткости можно потом утонуть. ну и тестится здесь проще же?

kuzmin_m11:08:36

мужик, там монстр на 1000 строк

kuzmin_m11:08:57

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

kuzmin_m11:08:30

единственоое нормальное решение - ранний возврат

anjensan11:08:39

так а зачем трогать рубиновый код? он идиоматичен для руби? если да (подозреваю что да, но python-er а не рубист), то и ок

kuzmin_m11:08:55

не пиши на языке

kuzmin_m11:08:00

а пиши с использование языка

kuzmin_m11:08:10

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

razum2um11:08:22

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

anjensan11:08:26

для clojure такой код не идеоматичен (всякие макросы с (return)) -- зачем впихнуть невпихуемое ?

kuzmin_m11:08:47

срачики - срачики =))

razum2um11:08:56

@anjensan some->/cond-> идеоматичны, я хз как жил бы без них

anjensan11:08:11

да ну... разбивать 1000 строк - это слишком сложно... 1к строк это вам не шуточки

kuzmin_m11:08:26

ага, и сделать из них 4000

kuzmin_m11:08:30

отличная идея)

anjensan11:08:52

ну 4к это вообще ужас... ты что там, космический корабль кодишь ?

kuzmin_m11:08:11

было

def move_link_to_packet_for_replaced_race(race)
    return if data.replaced_order_uid.blank?
    replaced_race = Race.find_by(uid: data.replaced_order_uid)
    return if replaced_race.blank?
    return if replaced_race.packet.blank?
    replaced_race.packet_attached_race.update! race: race
  end

kuzmin_m11:08:34

def move_link_to_packet_for_replaced_race(race)
    return if data.replaced_order_uid.blank?
    RaceWrap.new(Race.find_by(uid: data.replaced_order_uid)).move
end

class RaceWrap # some process class wrapping race maybe extended by some functionality further
  attr_reader :race

  def initialize(race)
    @race = race
  end

  def can_packet_attached_race?
    race.present? && race.packet.present?
  end

  def update
    race.packet_attached_race.update! race: race
  end

  def move
    update if can_packet_attached_race?
  end
end

razum2um11:08:05

@kuzmin_m да, не так лаконично.. но ты же понимаешь дополнительные плюсы второго?

kuzmin_m11:08:24

я вообще не воспринимаю это код

kuzmin_m11:08:36

я его не вижу просто

anjensan11:08:44

короче -- в рубях это все красиво. 1 строчка... return if ... в clojure -- нет

anjensan11:08:11

читающий такой код будет очень не рад и наверняка скажет не один раз "wtf"

anjensan11:08:30

и метрика "количество wtf на 1к строк кода" будет плохая

kuzmin_m11:08:30

пример афира видел?

anjensan11:08:21

ты про letr? да, видел, ужасно

kuzmin_m11:08:00

ну ладно

razum2um11:08:11

@anjensan не, метод move_link_to_packet_for_replaced_race правда не плох (за исключением имени), но я бы сказал, что тестируется хуже, а главное, что этот класс-враппер это же классика “extract class”. т.е. возможно того изначального метода вообще быть не должно, а враппер быть с нормальной семантикой процессом

anjensan11:08:49

@razum2um "метод" - ты про выделить класс в руби (я запутался чутка)

anjensan11:08:34

@kuzmin_m ты просто глянь на реализацию этого letr... не дай бог что пойдет не так (а я уверен что там куча корнер кейсов вылезет), ты будешь 100 раз проклинать что решил юзать такую магию

kuzmin_m11:08:05

я показываю проблему

razum2um11:08:07

“метод” = метод класс “def ..” в смысле

kuzmin_m11:08:25

а использовать сейчас хочу better-cond

kuzmin_m11:08:42

dom-top нравится меньше

anjensan11:08:51

вот часть реализации - там идет полноценный walk по всему коду, что ты написал... это черевато

anjensan11:08:36

используй ruby!

anjensan11:08:40

это будет еще продуктивнее

anjensan11:08:29

просто если ты пробуешь, учишь, играешься с clojure - имеет смысл для начала помучаться и юзать только clojure.core

anjensan11:08:50

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

anjensan11:08:30

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

anjensan11:08:26

у меня бы случился WTF увидь я такое в коде

anjensan11:08:43

(это пример из доков better-cond)

kuzmin_m11:08:15

ну а, <!! и >! норм? 😃

kuzmin_m11:08:38

просто не привычно

anjensan11:08:18

вообще да. норм... (<!! some-channel) -- очень даже норм

anjensan11:08:36

а вот do (println x) (без скобочек) -- это непонятно

fmnoise17:08:41

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