Fork me on GitHub
#clojure-sweden
<
2023-08-28
>
stefanvstein05:08:24

Gomorron gomorron

stefanvstein06:08:45

Känner du till något bibliotek som gör threading macros lite tydligare? Ibland vill man ha lite conditionals inbäddat i ett -> threadinguttryck. Det kan vara tydligare att ha dessa val up front direkt i threadinguttrycket än "gömt" i funktioner på annan plats. Själva threading uttrycket blir såklart enklare med funktioner på annan plats. Det är en fråga om smak. För mig, som brukar ta till det där med funktioner på annan plats, är problemet att dessa uttrck blir lite svåra att tyda. Svårt att tyda:

(let [outer {:foreign 3}]
      (-> {:apple 1 
           :banana 2}
         (#(if-let [banana (:banana %)]
              (assoc % :banana (+ 3 banana))
               %))
         (cond-> (:foreign outer)
            (assoc :banana (+ 2 (:foreign outer))))
         ((fn [item]
             (is (= {:apple 1 :banana 5}
                    item))
             item)))) 
Jag gjorde några macron, ->when-let, ->as-when-let, ->as, som gör motsvarande, lite enklare att tyda:
(let [outer {:foreign 3}]
    (-> {:apple 1 
         :banana 2}
      (->when-let [banana :banana] 
         (assoc :banana (+ 3 banana)))
      (->as-when-let fruits [another (:foreign outer)]
         (assoc fruits :banana (+ 2 another)))
      (->as item
         (is (= {:apple 1 :banana 5}
                item))
         item)))
...men. Second rule of macro-club: Don't write macros. Så känner du till något enklare, så att jag kan kasta mina macros. Annars får du gärna granska mina, om du nu känner behov av liknande https://github.com/stefanvstein/arrowish Tack för hjälpen

kardan12:08:44

Cool. Jag gjorde något inom samma område för länge sedan https://github.com/kardan/taxa

kardan12:08:52

Det var lite lek med macros och hierkier

stefanvstein12:08:16

Macron för att navigera träd?

kardan12:08:20

Kom från att ha sett logic by exception i api responses. Är kanske inte så relevant 😇

stefanvstein12:08:46

😄 Jag förstår inte vad det innebär. Men visst är det kul att skriva macros, eller meta-programmering över lag. Gjorde ett eget programmeringsspråk på 90-talet, bara för att det var kul. med lex och yacc och liknande verktyg. Sådan glädje när man hittar användning man inte tänkt på. Men också därför det är lite svårt med macros.

kardan13:08:54

Jag fattar inte heller - längre 🙂

cdeln06:08:06

God morgon. Kollat på cond-> ?

stefanvstein06:08:33

Naturligtvis, jag använder en cond-> i det lite svårare exemplet ovan. Det funkar ganska bra i många fall, men i detta fall får jag inte ett fritt värde av cond->:ens test, och måste köra det två gånger:

(cond-> (:foreign outer)
            (assoc :banana (+ 2 (:foreign outer))))

stefanvstein06:08:30

jämför

(->as-when-let fruits [another (:foreign outer)]
         (assoc fruits :banana (+ 2 another)))

stefanvstein06:08:00

Här får det trådade värdet ett namn: fruits, medan (:foreign outer) får heta another. Det kanske inte är så farligt att göra ett uppslag i en map. Men det skulle ju kunna finnas behov av att göra ett anrop med sidoeffekter, så man syntaktiskt "tvingas" göra anropet två gånger. Det finns många vägar runt detta, men här försker jag vara snäll mot den som vill göra det hela inline i -> uttrycket.

cdeln07:08:35

Ah, läste på mobilen så såg inte hela exemplet. Tycker att cond-> är cleant nog. Om man vill göra något advancerat med sidoeffekter så skulle jag refaktorera ut i en funktion.

cdeln07:08:30

Eller bara inte skriva trådade uttryck, det är inte alltid det snyggaste 😉

stefanvstein07:08:32

@U04JJEM2X1V Japp, håller med dig Carl. Jag har också en förmåga att skapa nya funktioner. Och ska det vara sidoeffekter så definitivt. Mitt problem är att jag snabbt blir förvirrad när jag ser kod som i mitt första exempel. "Å va betyder det här då"-känslan. Ibland får man kritik för att man refaktoriserat ut till en funktion, med argument att det är mer överblickbart med om valen är up front in i threading uttrycket. För mig är det något som kan åtgärdas med bra namngivning. Men jag tycker argumentationen är bra. Det finns ibland en fördel att se valen up front, och man har ju inte alltid tid att skriva tillräckligt bra kod.

cdeln07:08:09

Däremot eftersöker jag "threading modifiers". Det är inte alltid "thread first", "thread last" eller "thread as" går bra ihop. Skulle vilja ha ett sätt att mitt i ett trådat uttryck byta trådningsposition. Något som kanske finns i ditt libb? 🙂

cdeln07:08:52

(->> (map f xs) (filer pred) (first) <switch-to-thread-first> ...)

stefanvstein07:08:07

Ja, ->as, och ->>as ger möjlighet

stefanvstein07:08:48

Se där... Där åkte du på kodgranskning 🙂

cdeln07:08:42

Läser igenom exemplen i repot, fattar inte riktigt skillnaden på ->asoch ->>as . Kan du ge ett exempel där ->>as löser det ->as inte gör?

stefanvstein08:08:14

Skillnaden mellan ->as och ->>as är bara hur det läser argumenten. I ->as kommer värdet som ska få ett namn först, och i ->>as kommer värdet som ska få ett namn sist, piggybackat på på & form. ->>as passar i en ->>. Vi använder ->as och ->>as till att få ett namn på det trådade värdet. Med ett namn blir det enklare att byta riktning och mycket annat. Jag kan däremot tänka mig att man mycket väl skulle göra ett ->->> macro som skulle vara precis vad du var ute efter.. Men second rule of Macros...

stefanvstein08:08:59

(->> {:a 1 :b 2}
        (->>as {:keys [a] :as all}
               (is (= 1 a))
               all))

stefanvstein08:08:20

(-> {:a 1 :b 2}
        (->as {:keys [a] :as all}
              (is (= 1 a))
              all))

stefanvstein08:08:25

Kanske lite elakt exempel, då exmplet visar med destructuring i namngivningen. all är här hela mappen

stefanvstein08:08:41

@U04JJEM2X1V Kanske:

(-> [1 2 3]
  (conj 4)
  (->as all
    (->> all 
      (map inc)
      (filter even?)))
  (conj 1))
Skulle kunna tänka mig ett ->->> som trollar bort (->as all (->> all)) i ovan, helt enkelt

cdeln09:08:13

Ja exemplen är inte självklara. Nu ser jag hur det ska användas, tack. Ja precis, det är ett ->->> och ->>-> som jag eftersöker. Om det går så hade det vart najs om man kan skippa nesting också, tänker att det skulle så ut så här

cdeln09:08:20

(-> [1 2 3]
    (conj 4)
    (->->>)
    (map inc)
    (filter even?)
    (conj 1)

cdeln09:08:42

Pillade ihop ett uttryck med ->>-> också

cdeln09:08:47

(->> (map vector (map #(Math/round (Math/sin %)) (range 5)) (range 5))
     (filter (comp neg? first))
     (first)
     (->>->)
     (get 1)

stefanvstein14:08:56

Hmm..

(macroexpand '(-> 1
                inc
                ->-->
                (dec 3)))
blir
(dec (->--> (inc 1)) 3)
så det är lite svårt att se hur ->--> skulle påverka evalueringen av dec om man nu inte gör en egen -> som tolkar ->--> som en speciell symbol. Men man kan ju gör något i stil med:
(defmacro ->->> [value & form]
  `(let [value# ~value]
     (->> value#
          ~@form)))

(defmacro ->>-> [& form]
  (let [real-form (butlast form)
        value (last form)]
    `(let [value# ~value]
       (-> value#
           ~@real-form))))
för att få till den nestlade varianten av ->->> ->>->:
(-> [1 2 3]
   (conj 4)
   (->->> (map inc)
          (filter even?)
          (->>-> vec
                 (conj 5)))
   (conj 6))
Hade du nån annan tanke?

cdeln15:08:55

Nej du har rätt, man behöver definiera om -> och ->> för att få det att funka.

stefanvstein10:08:42

as-> är väldigt likt ->as i arrowish, och bra hjälp till konversationen med @U04JJEM2X1V. Saknar vi en as->> som namnger sista parametern?. Är cond->, cond->> och as-> tillräckligt för att skriva skön kod med inbäddade conditionals i threading uttryck?

emil0r15:08:16

Förstår inte varför man skulle behöva as->> :melting_face:

stefanvstein20:08:03

@U0545PBND as-> [expr name & forms] namnger den första parametern, expr, så att den är tillgänglig i forms. Den passar således bra att bryta ut det trådade värdet ur ett -> uttryck, som t.ex.:

(-> [10 11]
    (conj 12)
    (as-> xs (map inc xs))
    (vec))
Men om vi nu skulle arbeta med ett ->> uttryck. Då skulle vi vilja fånga upp och namnge den sista parametern istället. Det blir lätt lite otydlig kod.
(->> [10 11]    
     (#(if (< 1 (count %))
         (conj % (inc (last %)))
         %))
     (map inc)) 
Vad jag menade med as->> var då ett macro som namnger det sista parametern, så att man får ett uttryck som set ut som användandet av as-> i -> uttrycket, men i ett ->> uttryck.

stefanvstein20:08:07

Så att det blir

(->> [10 11]
  (as->> x (if (< 1 (count x))
              (conj x (inc (last x)))
              x))
  (map inc))

emil0r07:08:52

Varför bara inte göra?

(as-> [10 11] $
    (conj $ 12)
    (map inc $)
    (vec $))

stefanvstein07:08:17

Det kanske är det bästa sättet, att alltid utgå från as-> i grunduttrycket

stefanvstein07:08:49

Misstänker att man gärna gärna använder -> för att få till pointfree, eller tacit stil. Med as-> måste vi nämna punkten, $, hela tiden. Jämför att använda currying i ML språken. Men jag ska nog tänka på att börja använda as-> i grunduttrycket när jag ser dessa komplexa -> uttryck. Tack