Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yashaka/09264bfd8b933e6699cb7ea9c5d75957 to your computer and use it in GitHub Desktop.
Save yashaka/09264bfd8b933e6699cb7ea9c5d75957 to your computer and use it in GitHub Desktop.
Раздумывая о reversed версии кложурского comp (плюс немного мыслей по поводу ring+compojure)

Чего хочется?

Вот есть такой классический способ в клоуже:

(-> [[1 2] [:a :b]] (get 1) first) ; => :a

сдесь (get 1) вместо second просто ради примера использования функции с более чем одним параметром)

А мне нужен какой то такой вдобавок:

((fn-> (get 1) first) [[1 2] [:a :b]]) ; => :a

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

реализация проста (может я правда что то не учел...):

(defmacro fn-> [& forms]
  `(fn [x#]
    (-> x# ~@forms)))

Вопрос - есть идеи лучшего/более-очевидного имени?

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

(defmacro comp-> [& fns]
  `(comp ~@(reverse fns)))

ну или так (если без макросов):

 (defn comp-> [& fns]
   (apply comp (reverse fns)))

она удобна в тех случаях когда все функции, которые нужно скомпоновать в "натуральном порядке а не навыворот", - принимают только один параметр

также такая реализация будет более удобной в случае использования анонимных функций:

((comp-> #(get % 1) first) [[1 2] [:a :b]]) ; => :a

против более “скобастого":

((fn-> (#(get % 1)) first) [[1 2] [:a :b]]) ; => :a

Это я веду к тому что может удобно иметь оба варианта... Как тогда оба эти назвать?

Раньше я думал что comp-> это очевидно для простого "reversed comp", потому что стрелочка указывает на то что это тот же комп, но "составленный" последовательно с функций, поэтому и порядок изменится...

Теперь я понимаю, что у "очевидности" могут ноги расти и из того, что стрелочка - это таки всем уже известный макрос, и приставка comp - просто говорит о том что мы компонуем функцию из того что и раньше можно было в стрелочку-макрос запихнуть... Но при той реализации для comp-> что я показал выше - это не верно... значит таки лучше реализовать comp-> как:

(defmacro comp-> [& forms]
  `(fn [x#]
    (-> x# ~@forms)))

но тогда какое имя придумать для обычного "reversed comp"? - может просто rcomp какой то? другие идеи? 🙂 может есть другие "символы" которые могут быть очевидны как "инверсия"… ну или может таки забить нафиг на удобность

(defn comp-> [& fns]
   (apply comp (reverse fns)))

((comp-> #(get % 1) first) [[1 2] [:a :b]]) ; => :a

и оставить реализацию более скобастую и с ней уже и мириться:

(defmacro comp-> [& forms]
  `(fn [x#]
    (-> x# ~@forms)))

((comp-> (#(get % 1)) first) [[1 2] [:a :b]]) ; => :a

?

типа как оставить одну реализацию пускай иногда менее удобную - это более KISS...

Зачем все это нужно?

Например что бы в ring+compojure писать более стрейтфорвард и потому KISS код:

(defroutes app-routes
  (GET "/"        [] handlers/show-main)
  (GET "/login"   [] handlers/show-login)
  (POST "/login"  [] (comp-> params-request 
                             keyword-params-request 
                             handlers/do-login)))
(def app
  (-> app-routes
      (wrap-authentication auth/backend)
      wrap-session))     

вместо:

(defroutes app-routes
  (GET "/"        [] handlers/show-main)
  (GET "/login"   [] handlers/show-login)
  (POST "/login"  [] (comp-> params-request 
                             keyword-params-request 
                             handlers/do-login)))
(def app
  (-> app-routes
      (wrap-authentication auth/backend)
      wrap-keyword-params
      wrap-params
      wrap-session))   

Как видно в последнем варианте порядок wrap-keyword-params wrap-params менее натуральный, потому что навыворот. Плюс эти миддлвары навешаны на все роуты, несмотря на то что нужны только для одного.

Как то мне от всей этой обычной лапши в кольце мидлваров - не по себе. Выглядит как одна цепочка, в которой на самом деле есть куски маршрута которые нужно читать "по часовой стрелке/т.е. вниз" (обработчики респонса), есть "какие против/т.е. вверх" (обработчики реквеста) а есть какие "и туда и обратно" (как с wrap-session). И при этом все это нужно держать в голове, и не дай бог случайно не поменять порядок, а то все накроется медным тазом.

Мне прям сильно странно как это так люди в мире клоуже - бьют поклоны ring-у, и терпят такое извиняюсь безобразие:), прям "one noodles ring to confuse rule them all"

В идеале хотелось бы чего то такого (более однозначного, очевидного, и менее склонного к ошибкам):

(defroutes app-routes
  (ALL [] (comp-> session-request 
                  (authentication-request auth/backend))
                  
  (GET "/"        [] handlers/show-main)
  (GET "/login"   [] handlers/show-login)
  (POST "/login"  [] (comp-> params-request 
                             keyword-params-request 
                             handlers/do-login)))
(def app
  (-> app-routes
      wrap-session-response))     

Но для этого уже прийдется подшаманить в самом компоуже...

Кстати, можно конечно и без comp-> обойтись в этом случае:

(defroutes app-routes
  (ALL req (-> req session-request 
                   (authentication-request auth/backend))
                  
  (GET "/"        [] handlers/show-main)
  (GET "/login"   [] handlers/show-login)
  (POST "/login"  req (-> req params-request 
                              keyword-params-request 
                              handlers/do-login)))
(def app
  (-> app-routes
      wrap-session-response))     

Хотя мне кажется с comp-> более лаконично и читабельно, плюс насколько я видел код с компоуже - более идиоматично следующее

  (GET "/" [] handlers/show-main)

чем

  (GET "/" req handlers/show-main)

то есть не упоминать req там где можно без этого обойтись... хотя может это я уже и выдумываю...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment