This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-06-09
Channels
- # announcements (1)
- # babashka (14)
- # calva (8)
- # chlorine-clover (3)
- # clerk (6)
- # clj-kondo (27)
- # cljdoc (20)
- # clojars (6)
- # clojure (53)
- # clojure-denver (8)
- # clojure-europe (17)
- # clojure-nl (1)
- # clojure-norway (270)
- # clojure-uk (5)
- # clojurescript (35)
- # community-development (7)
- # cursive (12)
- # datalevin (3)
- # datomic (26)
- # etaoin (23)
- # exercism (1)
- # hyperfiddle (3)
- # java (14)
- # nrepl (2)
- # off-topic (12)
- # pathom (3)
- # portal (44)
- # practicalli (2)
- # reagent (7)
- # releases (1)
- # shadow-cljs (13)
- # timbre (3)
- # xtdb (4)
Hmm, noe i den tråden mellom @leif.eric.fredheim og @christian767 om @leif.eric.fredheim’s babashka adventur klikka i morges. I Clojure så er det veldig lett å gjøre:
(assoc foo :frobnitz (gazoonk whatever))
Og det er fint. Men i blandt, som @leif.eric.fredheim viste igår, så er det ikke alltid åpenbart hvorfor foo
trenger akkurat en frobnitz
akkurat her. Så da skriver man en kommentar:
;; the foo needs a :frobnitz to be able to initalize the frobnitz handler
(assoc foo :frobnitz (gazoonk whatever))
Men! Dette kan veldig godt være kodebasen din som forteller deg at det å legge til en frobnitz
er en viktig del av domenet ditt og dermed fortjener et top level navn dvs en fn med tilhørende doc stringJeg har i så måte blitt veldig glad i noe som man skulle kunne si ligner på getters/setters i Clojure
Og bare sånn at ikke @christian767 og @magnars får lunch-kaffen helt i vrangstrupen. Jeg synes kodebasen blir lettere å navigere (og lese) dersom man rett i nærheten av der man gjør f.eks:
(:frobnitz foo)
har en
(defn set-frobnitz [whatever v] (assoc whatever :frobnitz v))
gjerne med en docstring (og et bedre navn) slik at man lett kan se hvor i kodebasen man faktisk setter frobnitzer heller enn å måtte søke etter :frobnitz
og håpe på det beste.
Gitt namespace’a kw med doc-strings ville man sikkert oppnådd mye av det samme
(def frobnitzer "Does what any good old frobnitzer would do" ::my-ns/frobnitz)
Selvom jeg normalt sett blir litt lei meg når jeg må lage en funksjon som tar imot et map og bare gjøre assoc
eller update
(defn versioning-ctx
"Set a parameter in `ctx` that tells the postgres-repo mutation functions that care that they need to worry
about versioning."
[ctx]
(assoc ctx :suppress-versioning? false))
Ofte er løsningen at man heller lager en funksjon for å beregne verdien, og beholder assoc
/`update` på kallstedet. Men noen ganger er selve settingen som du sier viktig nok til å “løfte opp”
Jeg ville kalt den funksjonen get-versioning-ctx
- har fått kjeft av @magnars for funksjoner med substantiv-navn mange nok ganger til å være enig i at det er bedre med et verb 😄
Jeg bruker konsekvent ikke set
fordi jeg ser for meg at @magnars en eller annen gang blir innleid som konsulent i Ardoq…
makan. her har jeg lært meg å unngå en type kjeft bare for å løpe rett i inn i en annen 😄
Jeg har lagt meg på en konvensjon med ->
-prefiks når en funksjon transformerer input til noe annet: ->versioning-ctx
.
@U06BEJGKD ja, jeg bruker også -> når det er transformasjoner
men eksempelet til Erik legger til informasjon, så det er ikke en ren transformasjon i mine øyne
Man kunne jo også set for seg noe som:
(def versioning {:suppress-versioning? false})
og så kunne man hatt:
(assoc ctx whatever/versioning)
(defn enable-versioning [ctx]
(assoc ctx :versioning? true))
(defn suppress-versioning [ctx]
(assoc ctx :versioning? false))
Deilig å bli kvitt både dobbel negativ og frigjøre seg fra kingdom of the nouns i en liten forandring.
Men, uansett, jeg synes hovedpoenget holder. Av og til er det verdt å navngi og løfte opp operasjoner som egentlig bare er en assoc
fordi det gjør kodebasen enklere å lese/navigere/forstå
Altså, uten docstrings, så syns jeg fortsatt det er meningsløst. Men som en "docstring receptacle" så syns jeg det er helt fint.
Og det gir rom for å utvide senere. Det gir et level of indirection eller et interface, om du vil.
Det kan du si, men da er det kjedelig å løpe rundt på alle steder jeg har assoc :foo bar
og legge til assoc :qux bla
Hvis vi ikke hadde hatt denne funksjonen, så måtte man (gitt dårlige docstrings) lest implementasjonen for å skjønne at hvis man ville gjøre noe med hvordan vi versjonerte ting så kunne man legge til :suppress-versioning? false
på ctx
Nå ser man fra top-level defs at det er en av kapabilitetene til dette ns’et
og så slipper man å huske på at her er det jammen dobbelt negering som gjelder, og at det heter suppress
og ikke disable
hvorfor ikke flytte det et lag over
(defn update! [ctx thing]
(foobar-db ctx thing))
(defn skip-version-update! [ctx thing]
(-> (skip-versioning-ctx ctx)
(update! thing)))
Da slipper man å måtte være klar på at man må oppdatere ctx for å skippe/enable versioning?Forslaget til @marius.enerly oppnår også å flytte informasjonen om versjonering til der du skal committe, altså nærmere der det har noe å si 👌
Men jeg sier meg også enig i poenget til @slipset: Noen ganger er det ålreit å løfte rett frem datamanipulering inn i en funksjon, som en slags reklameplakat.
som reklameplakat og docstring-receptacle 👍 utover denne bruken bør man i høyeste grad unngå å ha assoc som første form i en funksjon.
henger meg litt opp i navnene her og jeg egentlig. Hva med litt namespaced keys sånn at navnet gir mening på egne bein? Det er ihvertfall en annen approach til problemet “hva i alle dager beryr :frobnitz
i denne konteksten”
Noob spørsmål her…
Jeg synes det er fint å bruke ->
og ->>
for å lage "pipelines" av funksjoner. Men det er noen ganger vanskelig, fordi enkelte funksjoner forventer første parameter, og andre forventer siste parameter. Da står jeg litt fast, fordi det er enten-eller med ->
eller ->>
.
Finnes det en "thread-first-or-last" macro også? :thinking_face:
Eller må jeg lage en liten "wrapper-funksjon" for å endre rekkefølgen på paramenterne til "inkompatible" funksjoner?
Jeg ser det finnes cond->
men er ikke helt sikker på om den kan brukes til noe slikt.
Jeg pleier å bestemme meg for å bruke ->> eller ->, og så skrive de funksjonene som mangler. Med thread first må jeg ha funksjoner som tar tingen som første argument.
Ja, det er det jeg holder på med også, men jeg trodde kanskje jeg holdt på med noe dumt.
Clojure har en tydelig, men underkommunisert konvensjon: collections går i thread last, maps I thread first. Core apiet er veldig konsistent på dette
Dersom du allikevel får lyst til å bytte threading: bryt opp, se om du kan plukke ut funksjoner osv
cond-variantene et threading med conditionals for hvert steg, nyttig til sitt bruk
> Istedet bryter du opp koden. Når du går fra et map til en seq, bryt opp threadingen. Når du går fra en seq til et map, bryt opp. Det er lov å koste på seg et navn. Point free programming er kult det, men ikke dra det for langt da. > Huh, jeg hadde ikke koblet threading-makroene med point free style. Men det er jo det samme, det! 😊
@teodorlu dårlig eksempel på cond->
:
(cond-> {}
raining? (assoc :weather :raining)
cold? (assoc :temps :low))
(cond-> strategy
hour-minute
(assoc :rule/hour-minute hour-minute)
(:strategy/from-ldt strategy)
(assoc :rule/from (.atZone (:strategy/from-ldt strategy) (:zid ctx)))
(:strategy/to-ldt strategy)
(assoc :rule/to (.atZone (:strategy/to-ldt strategy) (:zid ctx))))
Jaaa, dette er lurt, tror jeg. Jeg har noe kode der jeg overskriver samme variabel flere ganger i en let.
(let [
x {}
x (if y (assoc x :y y) x)
,,,]
,,,)
Lukter cond->. (dumt formatert, sitter på mobilen)En annen (fin) bruk for cond->
er hvis du finner at du skriver kode som
(if whatever
(assoc foo :bar baz)
foo)
Dette løser du med cond->
(cond-> foo
whatever (assoc :bar baz))
Her er en annen klassisk artikkel om temaet: https://stuartsierra.com/2018/07/06/threading-with-style
To små gotchas med cond->
1. Du threader ikke gjennom condition’en
2. Den exiter ikke tidlig slik som cond
gjør
Det siste punktet betyr at hvis du har flere conditions som er true, så threades verdien din gjennom alle.
Ja. Eller > Fordi vi har some-> og nil-punning trenger vi ikke å tenke på monader > ?
Jo, det stemmer nok, men jeg synes det er kult å se hvordan ideene dukker opp i forskjellige implementasjoner. Clojure er jo befriende fritt for applicatives, functors og alt det der, men det er jo der. Jeg tror map
f.eks er en functor og at en applicative er den funksjonen du sender inn til map
Min forståelse: • Functor er ting du kan kjøre map på. Feks clojure sequences • Applicative skjønner jeg ikke helt, men tror det har noe med parallellisering å gjøre
lurer på om grunnen til at jeg ikke skjønner monader, og grunnen til at jeg foretrekker dynamiske språk, er den samme tingen (uten at jeg har innsikt i hvorfor eller hvordan eller noe som helst)
min tommelfingerregel der er at når det behovet melder seg, kan det være greit å stykke ting opp litt kanskje 🙂
Jeg tror dette libet er det du ser etter Leif https://github.com/randomcorp/thread-first-thread-last-backwards-question-mark-as-arrow-cond-arrow-bang
Litt off-topic, men jeg bor på hostel her på Granka, basically private rom med felles kjøkken og bad. Videre, vi er litt nervøse for at eldstesønnen på 16 skal fly til Køben og klare å surre seg fram til en fekte-camp i sommer. Og også surre seg hjem igjen. Derfor følger kona med og sørger for at han kommer seg til camp’en, og så får han komme seg hjem på egen hånd. Han skal være borte ca en uke - full kost og losji I går ramla det inn to japanere her. Den ene 16 og den andre 13. De hadde flydd 27 timer fra Japan, mellomlanda i Istanbul og Madrid. Skal være her i en måned. Lagde seg middag da de kom og kjørte på med pølser, ostesmørbrød, speilegg, salat og banan til frokost. Litt av en kontrast til mine barn.
Jeg er dobbelt statsborger (norsk-amerikansk) og har flydd alene for å besøke familien i USA mange ganger siden jeg var 6 år gammel 🙂 Med ledsager fra flyselskapet da vel å merke.
Og fordi kona mi er japansk, er jeg over middels interessert i japansk språk og kultur. Hver gang vi reiser dit blir jeg overrasket av å se førsteklassinger reise med toget alene til skolen, osv. Barn har mye mer selvstendighet der.
har sett søte (og sikkert sanne?) poster om hvordan det er en kultur i Japan for at alle voksne har et slags felles ansvar på å hjelpe og passe på barn som er alene
jeg hadde veldig lyst til å løfte nettverkingkompetansen min når jeg søker etter jobber med noen gode tips for å skrive e-poster jeg sender til folk jeg ikke har kontakt med fra før, men så ble jeg avbrutt av linkedin med en bursdagsmelding i stedet for 😄
> jeg hadde veldig lyst til å løfte nettverkingkompetansen min når jeg søker etter jobber med noen gode tips for å skrive e-poster jeg sender til folk jeg ikke har kontakt med fra før Dette er nok en av de tyngre setningene jeg har lest på en stund. Ikke helt sikker på om jeg forstår hva du sier 🙂
burde trykket på linken ved siden av bursdagsmeldinga på linkedin egentlig, jeg ble så gira av bursdagen at jeg helt glemte å benytte meg av det flotte tilbudet LinkedIn kom med der
her var den. Nå skal tilbudene formerlig renne inn til Oslo CRUD og Business, tenker jeg! https://www.linkedin.com/news/story/making-cold-emailing-work-5301969/
Jeg fant muligens https://github.com/babashka/fs/issues/105 i dag 😊
Nå ble det fryktelig til spam på meg i dag, men… Jeg har endelig fått mitt første Babashka script til å gjøre det jeg ville! Men det er potensielt mye dumt og/eller stygt der; rom til forbedringer. Er det noen som har tid til å ta en kjapp titt på denne for å gi meg feedback på hva jeg bør gjøre annerledes? https://github.com/leifericf/leifs-utils/blob/main/src/leifs_utils/core.clj
Hvis jeg skulle kommentere på noe, så er det vel at:
(process/sh "az devops project list --org" org-url)
:out
(json/parse-string true)
og
(process/sh "az repos list --project" project-id)
:out
(json/parse-string true)
Ligner litt mye på hverandre?Ja, enig! Godt observert. Jeg tenkte også på det, men var usikker på hvordan jeg skulle fikse det. Tenkte kanskje det fantes et lurt triks med partial
og comp
eller defmacro
, men jeg tenkte ikke på å bare lage en enkel funksjon for de delene som er helt like, slik du foreslår.
(defn process-out->json [something other]
(-> (process/sh something other) :out (json/parse-string true)))
usikker på hvordan process/sh
virker om den bare tar en gjeng med args eller om du kunne har gjort (str az repos list --project " project-id)
og sendt det til process/sh
Men, kanskje det aller viktigste! Dette virker bare for de første 16 (eller 32?) repo’ene 🙂
Jøss! Det kan se sånn ut, ja, men jeg forstår ikke hvorfor 😅 Hvorfor prosesserer ikke map
hele lista med alle repo URL-er? :thinking_face:
map er lazy og realiserer chunks på 32 entries om gangen. Så om du feks ber om first så vil den mappe 32 items, men ikke mer.
På grunn av dette bør du aldri bruke map til sideeffeekter. Bruk doseq i stedet.
Jøss! Takk! Det ville tatt meg lang tid å finne ut av på egenhånd når jeg omsider hadde oppdaget feilen 😅
Spesielt fordi det ser ut som om det fungerer om du kjører det i REPL-et: REPL-et vil realisere collectionen for å vise den til deg 😅
(defn clone-all-repos
"Clone all Git repos from Azure DevOps."
[] (->> (get-all-repo-data)
(tree-seq coll? identity)
(keep :sshUrl)
(map clone-repo)))
Burde nok skrives om til enten en doseq
eller en run!
(defn clone-all-repos
"Clone all Git repos from Azure DevOps."
[] (->> (get-all-repo-data)
(tree-seq coll? identity)
(keep :sshUrl)
(run! clone-repo)))
run!
var relativt enkelt å bruke, fordi jeg kunne simpelthen bytte ut map
med run!
. Men hvordan bruker jeg doseq
i denne thread-last makroen? I og med at doseq
trenger en vector med bindings. Må jeg pakke inn hele thread-last makroen i doseq
på et vis, eller skrive om hele body uten å bruke thread-last makroen? :thinking_face:
(defn clone-all-repos
[]
(->> (get-all-repo-data)
(tree-seq coll? identity)
(keep :sshUrl)
(run! clone-repo)))
Ahaaa, okay! Skjønner. Så det har egentlig ingen praktisk betydning hvorvidt jeg bruker run!
eller doseq
her, fordi det er to "flavors" av samme mekanisme på en måte?
Så fantastisk! Jeg satt og reiv meg i håret på hvordan jeg skulle bruke doseq
også har jeg allerede gjort det uvitende 😛
Du ble sikkert forvirret av at jeg kun snakket om doseq
, for det er den jeg pleier å ty til for side-effekter. Men det er bare tilfeldig at det har blitt sånn for meg 🙂
En ting jeg la merke til: Når jeg bruker map
så får jeg masse output i REPL, men ikke når jeg bruker run!
. Med run!
får jeg ingen output som viser at det skjer noe som helst, men jeg ser i mappa at ting skjer.
Ja, jeg ble litt forvirret av det, og fordi @slipset nevnte begge i sin melding, så tenkte jeg feilaktig at det var to forskjellige alternativer.
Det er nokså vanlig at man i funksjonelle språk skiller på retur-verdier og side-effekter. Funksjonen din bør helst ha én av dem.
(doseq [url (->> (get-all-repo-data)
(tree-seq coll? identity)
(keep :sshUrl))]
(clone-repo url))
Ahaaa! Man må putte hele thread-last i selve bindingen, ja. Jeg glemmer at en kan gjøre mer avanserte greier inne i binding.
Så hvis jeg forstår rett så er run!
en eager versjon av map
, og doseq
er en eager versjon av for
i den forstand at de førstnevnte "tvinger ut" hele sekvensen uansett hvor stor den er, mens de sistnevnte evaluerer kun det som er nødvendig (avhengig av hvordan returverdien blir brukt).
Nå får sikkert @christian767 morgenkaffen i vrangstrupen sin, men hvis du vil ha både sideeffekter og returverdier, så er reduce
(eller grøss mapv
) vennene dine.
Skjønner! Takk for tålmodigheten og at du gidder å forklare ting på en n00b-vennlig måte 😅
Jeg skal til og med by på et ålreit use-case for doall
: Du kan bruke den sammen med pmap
for å parallellisere arbeidet:
(->> (get-all-repo-data)
(tree-seq coll? identity)
(keep :sshUrl)
(pmap clone-repo)
doall)
Nå spørs det hvor happy Azure DevOps CLI er med at jeg kjører masse clones samtidig, men det finner vi fort ut av 😅
pmap
var en av tingene som solgte meg på Clojure. Å skrive parallell kode har liksom alltid vært skummelt. I Clojure var det bare å legge til en p
og det var det. Fordelen og ulempen med pmap
er at den er veldig lite tune-bar, faktisk ikke i det hele tatt.
@leif.eric.fredheim Siden du er i utforskermodus, anbefaler jeg https://clojure.org/api/cheatsheet. Funksjonene dere er fint gruppert under forskjellige overskrifter. doseq
med venner finner du under Sequences → Using a Seq → Force evalutation.
@U06BEJGKD: Den er fin! Mange takk!
@christian767 Ref. eksempelet i https://clojurians.slack.com/archives/C061XGG1W/p1686552282934989?thread_ts=1686322032.104469&cid=C061XGG1W med pmap
+ doall
. Hvis jeg forstår rett, trenger man ikke å bruke doall
her, fordi pmap
i seg selv er "eager." Stemmer det?
> “Like map, except f is applied in parallel. Semi-lazy in that the > parallel computation stays ahead of the consumption, but doesn’t > realize the entire result unless required. Only useful for > computationally intensive functions where the time of f dominates > the coordination overhead.”
Så rart! Når jeg fjernet doall
fra koden min så ser jeg ingen forskjell når jeg bruker pmap
. Det ser ut til at hele collection blir "realisert" uansett.
Det er godt å høre at selv dere erfarne folk snubler i de samme trådene noen ganger da! Gir meg litt peace of mind 😂
med for
kan man løse det ved å bytte ut med doseq
som har samme signatur (men returnerer nil)
Jeg foretrekker doseq
fordi den også veldig tydelig kommuniserer at koden gjør side-effekter
det blir litt som å bruke when
istedenfor if
hvis man har bare én klausul. Den reduserte friheten kommuniserer tydeligere.
Affirmative. Min nye tommelfingerregel blir heretter "prefer doseq
for imperative shit"
@slipset skal ikke være så bastant, men har ikke selv hatt det behovet. Som oftest er det data på vei inn som kan inspiseres. Best å gjøre så lite som mulig inni selve "do shit"-loopen.
Men en ting jeg merker nå… Jeg har denne versjonen istedenfor, som funker fett!
(defn clone-all-repos
[repos]
(doseq [url (extract-key :sshUrl repos)]
(clone-repo url)))
"Problemet" er at den er mye treigere, antagelig fordi doseq
ikke er parallellisert. Og den kjører mange git clone
commands, som kan ta litt tid hver seg.
Hva er "best practice" for "do-shit-loops" med side-effects hvis det hele skal parallelliseres? Er det da en drar fold
ut av skuffen?Hvis du ikke trenger kontroll over timingen så er kanskje pmap + doall like greit. Ellers har du mer styring hvis du lager din egen lille maskin med core.async.
Ja, i dette tilfellet trenger jeg ikke kontroll over timingen. Jeg vil bare clone alle repos raskest mulig egentlig.
Men! Jeg har faktisk også opplevd en merkelig runtime error et par ganger med pmap
+ doall
. Får randomly en eller annen Java threading error, så blir den borte når jeg kjører på nytt.
Det er kanskje det enkleste. Da får du like mange parallelle nedlastinger som pmap bruker internt, hva enn det er. Hvis du vil ha kontroll over graden av parallellitet kan du bruke core.async, som @magnars nevnte (`pipeline-blocking` f.eks.), eller du kan sette opp en egen Executor (thread pool) som du konfigurere akkurat som du vil (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int-). For ditt formål her og nå er det kanskje å gå alt for langt, men nyttig å vite om.
Nice, takk for tipset! Ja, jeg tror 32 parallelle nedlastninger (eller hva det nå blir) er godt nok her. I mitt tilfelle er det færre enn 1000 repos, så det går ganske radig med pmap
. Typ under 10 sekunder.
Jeg fikk litt problemer med at doseq
inni en funksjon gjorde at funksjonen heller ikke returnerte korrekt verdi, og jeg fikk "unused value" warnings av clj-kondo. Så jeg forsøkte meg på reduce
istedenfor. Gir https://github.com/leifericf/leifs-utils/commit/b63d3458534afa6dbc694a986f4623aebe6ad4c2 mening? :thinking_face: Når man må "fange resultatet" for å returnere det fra funksjonen, slik @slipset var inne på tidligere. Det funker, men jeg synes det er litt vanskeligere å lese.
Hvis jeg ville bruke doseq
— Hva kan jeg bruke på slutten for å realisere alt? Sånn at funksjonene returnerer resultatet av doseq
. Hvis det gir mening.
(->> (file/glob search-path "**.git" {:hidden true})
(map file/parent)
(map str)))
Er helt fint. ikke noe grunn til å bruke reduce/doseq her, så lenge det er noe annet lenger ut som bruker reduce/doseq for å realisere det som blir produsert her.Eller, hvis du ikke vil briefe med at du kan reduce:
(mapv str xs)
Men vi ble vel enige tidligere om at å bruke mapv
bare fordi du vil realisere en lazyseq er litt styggedomAha, jeg tenkte at siden file/glob
gjorde "side-effecting shit," så burde man ikke bruke map
inne i den funksjonen heller 😅
Det kan du fint gjøre, bare husk at exceptions da kommer når du realiserer sekvensen, dvs på et annet sted enn der du ville forvente.
Oh, right! Mye å lære seg merker jeg! I dag var ikke en spesielt produktiv, men lærerik dag 😛
Det neste spørsmålet som poppet opp i hodet mitt da er: Hvor langt "ned" bør man realisere sekvenser? Intuitivt tenker jeg at de bør realiseres så langt "opp" som mulig der de brukes og ikke i selve implementasjonen av funksjonen som produserer dem :thinking_face: Lukter at det kanskje finnes en ny tommelfingerregel til @U0MKRS1FX sin neste bok her et sted.
Hvis jeg forstår deg rett: Funksjonen foo
returnerer en sekvens via map
. Funksjonen bar
kaller foo
og vil printe ut alt i sekvensen fra foo
. Da er det bar
sin jobb å realisere sekvensen. Korrekt?
Tenk deg:
(defn foo [x]
(range (+ 10 x)))
Denne returnerer en uendelig lazy seq av tall
(defn bar [n xs] (println (take n xs)))
Da kan duSå enkelt, men så vanskelig å forstå også av en eller annen grunn. Jeg må pakke ut noen eksempler forsiktig i REPL i morgen.
lazy er rått. Det muliggjør en god del forenkling og færre linjer kode https://augustl.com/blog/2019/amazing_lazy_data_structures/
Ettersom ingen har svart på spørsmålet ditt enda, @leif.eric.fredheim - det er doall
du kaller på en lazy seq for å realisere den.
nitpick, men kode ved siden av argument-lista ser rart ut for meg. jeg ville gjort sånn:
(defn clone-all-repos
"Clone all Git repos from Azure DevOps."
[]
(->> (get-all-repo-data)
(tree-seq coll? identity)
(keep :sshUrl)
(run! clone-repo)))
Nitpick er bra! Man lærer av sånt. Jeg går faktisk mye frem og tilbake på dette og er ikke sikker på hva jeg foretrekker, spesielt når funksjoner har multiple arities, f.eks.:
(defn clone-repo
"Clone a Git repo to a destination path."
([repo-url] (clone-repo repo-url (str (file/home) (get-secret :repo-root-dir))))
([repo-url dest-path] (let [path dest-path]
(if-not (file/exists? path) (file/create-dir path) nil)
(process/sh {:dir path} "git clone" repo-url))))
Jeg synes det er lettere å lese det når body kommer rett etter argument-lista den "tilhører" når det er flere implementasjoner, spesielt hvis noen arities har korte bodies. Men det kan bare være fordi jeg er n00b og ikke har vendt meg til å lese Clojure-kode helt enda.Dette blir veldig subjektivt og synsete, men jeg er generelt var for ting som skyver koden ut til høyre. Det gir dårlig utnyttelse av plass og kan gjøre det vanskeligere å splitte skjermen. Jeg bruker svært sjelden multi-arity, men om jeg gjør det så bruker jeg heller en blank linje for å skille arityene. Dessuten er de pakket i sin egen parantes og kan pared-navigeres.
jeg løser også dette med “newline galore”. Jeg er glad i whitespace 🙂
(defn clone-repo
"Clone a Git repo to a destination path."
([repo-url]
(clone-repo repo-url (str (file/home) (get-secret :repo-root-dir))))
([repo-url dest-path]
(when-not (file/exists? path) (file/create-dir path))
(process/sh {:dir path} "git clone" repo-url)))
(og alle steder jeg kan bruke when og when- i stedet for if og if- blir jeg glad og fornøyd)
Satser på det! 😊 Tok en prøvekjøring i lokalet med en fyr som var villig til å høre på. Veldig glad jeg gjorde det. Fikk plukket ut noe rusk. Har mest slides, men skal vise bittelitt fra Emacs også. Hvis vi ikke hadde kjørt testen, hadde jeg hatt aaaaalt for liten font i Emacs. Størrelse 20 ("huge") holdt ikke. Nå har jeg:
(defun teod/font-megahuge ()
(interactive)
(setq doom-font (font-spec :family "monospace" :size 40))
(doom/reload-font))
Nitpick er bra! Man lærer av sånt. Jeg går faktisk mye frem og tilbake på dette og er ikke sikker på hva jeg foretrekker, spesielt når funksjoner har multiple arities, f.eks.:
(defn clone-repo
"Clone a Git repo to a destination path."
([repo-url] (clone-repo repo-url (str (file/home) (get-secret :repo-root-dir))))
([repo-url dest-path] (let [path dest-path]
(if-not (file/exists? path) (file/create-dir path) nil)
(process/sh {:dir path} "git clone" repo-url))))
Jeg synes det er lettere å lese det når body kommer rett etter argument-lista den "tilhører" når det er flere implementasjoner, spesielt hvis noen arities har korte bodies. Men det kan bare være fordi jeg er n00b og ikke har vendt meg til å lese Clojure-kode helt enda.Jøss! Det kan se sånn ut, ja, men jeg forstår ikke hvorfor 😅 Hvorfor prosesserer ikke map
hele lista med alle repo URL-er? :thinking_face:
run!
var relativt enkelt å bruke, fordi jeg kunne simpelthen bytte ut map
med run!
. Men hvordan bruker jeg doseq
i denne thread-last makroen? I og med at doseq
trenger en vector med bindings. Må jeg pakke inn hele thread-last makroen i doseq
på et vis, eller skrive om hele body uten å bruke thread-last makroen? :thinking_face:
(defn clone-all-repos
[]
(->> (get-all-repo-data)
(tree-seq coll? identity)
(keep :sshUrl)
(run! clone-repo)))
@christian767 Ref. eksempelet i https://clojurians.slack.com/archives/C061XGG1W/p1686552282934989?thread_ts=1686322032.104469&cid=C061XGG1W med pmap
+ doall
. Hvis jeg forstår rett, trenger man ikke å bruke doall
her, fordi pmap
i seg selv er "eager." Stemmer det?