This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-08-28
Channels
- # announcements (7)
- # beginners (49)
- # calva (18)
- # cider (48)
- # clj-yaml (1)
- # clojure (37)
- # clojure-dev (23)
- # clojure-europe (29)
- # clojure-nl (1)
- # clojure-norway (16)
- # clojure-sweden (41)
- # clojure-uk (6)
- # clojurescript (32)
- # community-development (9)
- # conjure (4)
- # datomic (13)
- # hyperfiddle (24)
- # kaocha (6)
- # leiningen (4)
- # missionary (46)
- # nextjournal (1)
- # nrepl (26)
- # off-topic (101)
- # shadow-cljs (67)
- # testing (9)
- # yamlscript (5)
Gomorron gomorron
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älpenCool. Jag gjorde något inom samma område för länge sedan https://github.com/kardan/taxa
Macron för att navigera träd?
😄 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.
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))))
jämför
(->as-when-let fruits [another (:foreign outer)]
(assoc fruits :banana (+ 2 another)))
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.
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.
@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.
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? 🙂
Ja, ->as, och ->>as ger möjlighet
Se där... Där åkte du på kodgranskning 🙂
Läser igenom exemplen i repot, fattar inte riktigt skillnaden på ->as
och ->>as
. Kan du ge ett exempel där ->>as
löser det ->as
inte gör?
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...
(->> {:a 1 :b 2}
(->>as {:keys [a] :as all}
(is (= 1 a))
all))
(-> {:a 1 :b 2}
(->as {:keys [a] :as all}
(is (= 1 a))
all))
Kanske lite elakt exempel, då exmplet visar med destructuring i namngivningen. all är här hela mappen
@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 enkeltJa 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
(->> (map vector (map #(Math/round (Math/sin %)) (range 5)) (range 5))
(filter (comp neg? first))
(first)
(->>->)
(get 1)
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?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?
@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.Så att det blir
(->> [10 11]
(as->> x (if (< 1 (count x))
(conj x (inc (last x)))
x))
(map inc))
Det kanske är det bästa sättet, att alltid utgå från as-> i grunduttrycket
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