This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-06-20
Channels
- # ai (4)
- # aleph (1)
- # babashka (127)
- # beginners (89)
- # calva (44)
- # cider (22)
- # clerk (74)
- # clj-commons (5)
- # clj-kondo (3)
- # cljs-dev (51)
- # clojure (117)
- # clojure-europe (22)
- # clojure-nl (2)
- # clojure-norway (100)
- # clojure-uk (2)
- # clojurescript (64)
- # data-science (26)
- # datalevin (3)
- # datascript (2)
- # emacs (10)
- # events (5)
- # figwheel-main (12)
- # helix (2)
- # honeysql (15)
- # hoplon (3)
- # jobs-discuss (32)
- # malli (3)
- # polylith (3)
- # re-frame (2)
- # reitit (15)
- # releases (2)
- # sci (14)
- # shadow-cljs (14)
- # specter (2)
- # tools-build (7)
- # xtdb (16)
Jeg har prøvd meg litt på å skrive makroer, men jeg bruker i dag ingen av makroene jeg har skrevet. Det meste kunne løses med funksjoner, data og makroer andre har skrevet. Når var forrige gang du brukte en makro du selv har skrevet?
(defmacro forcat
"`forcat` is to `for` like `mapcat` is to `map`."
[& body]
`(mapcat identity (for ~@body)))
særlig kjekt når man driver og komponerer f.eks funksjoner som returnerer ting som skal sendes inn til datomic/datascript transactions 😄
er egentlig generelt mange settinger hvor det er digg å kunne ha en samling funksjoner som returnerer lister, så du kan ha funksjoner som returnerer 0, 1 eller mange av noe uten å endre for mye på ting, og så mapcatte det sammen til slutt etterpå
Jeg fikk en "dobbel liste" i min funksjon sh->out
når den ble kalt fra sh-out->json
:
(defn sh->out
[opts & args]
(-> (apply process/sh opts (flatten args))
:out))
(defn sh-out->json
[opts & args]
(-> (sh->out opts args)
(json/parse-string true)))
Klarte ikke å finne ut hvorfor, så jeg var lat og brukte flatten
😅 Sikkert ikke lurt.nemlig, ofte så vet du hvor mange nivåer du vil “ta bort”, og kan bruke f.eks mapcat i stedet for
dobbel liste her kommer vel forøvrig fra & args “to ganger. Ved første kall er args en liste, så sender du den inn til en annen som er & args og da får en liste med det første argumentet, som er en liste 😄
Men hvorfor blir lista pakken inn i en annen liste når den allerede var en liste? 😅
& args er ikke “smart”, den får inn et agument som er en liste, og vil ikke automatisk explode den basert på type
kan eventuelt bruke apply - ((fn [& args1] (apply (fn [& args2] args2) args1)) 1 2 3)
=> (1 2 3)
sh-out->json
brukes slik, så tror den må være variadic:
(defn get-devops-project-repo-data
[project-id]
(sh-out->json "az" "repos" "list"
"--project" project-id))
(defn get-github-repo-data
[]
(sh-out->json "gh" "repo" "list" (:github/org-name settings)
"--language" (:github/repo-language-filter settings)
"--source"
"--no-archived"
"--limit" "1000"
"--json" "sshUrl"))
sh->out
brukes slik nå:
defn clone-repo
[repo-url]
(let [path (get-repo-root-path)]
(if-not (file/exists? path) (file/create-dir path) nil)
(sh->out {:dir path} "git" "clone" repo-url)))
(defn run-git-command
([command]
(run-git-command (find-repo-paths (get-repo-root-path)) command))
([root-path command]
(->> root-path
(pmap #(sh->out {:dir %} "git" command))
(doall))))
(defn sh-out->json
[opts & args]
(-> (apply sh->out opts args)
(json/parse-string true)))

Lurte på om jeg kanskje kunne brukt multiple arities eller multimethods for å unngå to separate funksjoner.
synes egentlig det virker greit å ha to funksjoner sånn, en for et “rent” kall, og en som kaller den rene og gjør noko attått
Men fikla med det lenge og slo meg til ro med to funksjoner for å komme videre med det jeg prøvde å oppnå 😅
i JS har man syntaks for sånt, man kan explode med …
. apply
gjør samme nytta i clj 😄
men, ofte synes jeg det er greit å rett og slett bare sende inn ei liste, nettopp pga ting som dette, og at noen ganger kan det være kjekt å kunne bygge opp en liste programmatisk og sende det inn til funksjonen uten å måtte kalle apply på den først osv
altså at man bare kaller den med (sh-out->json "gh" ["repo" "list" x "--source"])
etc. En smakssak 👍
Jeg tror ikke det vil fungere pga. Babashka sin interne "tokenizer" gjør noe fancy shit for å konvertere lista med strenger til shell script stuff. Men jeg skal teste litt!
Fikk litt hjelp i #CLX41ASCS tidligere og selveste borkdude sa jeg bare burde sende inn en liste med strenger til babashka/sh
.
bør vel ikke ha noe å si om du sender inn en “args” som en liste sånn, eller varargs med “& args” (om det ga mening)
selve kallet til sh må kanskje kalles med varargs ja, men du kan jo velge å gjøre det annerledes i dine egne funksjoner
Jeg bruker også denne en del i main-metoden av appen min:
(defmacro with-timing [name exp]
`(do
(print "Time for" ~name "... ")
(flush)
(let [start# (System/currentTimeMillis)
res# ~exp]
(println (- (System/currentTimeMillis) start#) "ms")
res#)))
I min erfaring blir makroer blir veldig oversolgt når man først setter seg inn i Lisp. Det er sjelden kost. Nyttige når de er løsningen, men bør brukes sparsomt.
Hva er det den gjør?
(with-test-system [sys (test-system)]
,,,)
? Antar det ikke er akkurat det den gjør, for da kunne man vel
(let [sys (test-system)]
,,,)
makroer putter jeg i seksjonen for “språk-funksjon som er nyttig for de som lager libraries” 😄 Føler alle språk er litt seksjonerte der. Ikke så mange som bruker contracts i Kotlin i CRUD-en sin liksom
men uten makroer hadde vi jo ikke hatt core.async og defscene i portifolio og andre nyttige greier!
and
og or
drar jo nytte av å være macros, er vel sånn den evaluerer én og én ting som sendes inn etter tur, i stedet for å evaluere alle argumentene først og så gå igjennom de
Jeg tenkte på makroer fordi det kom et https://clojurians.slack.com/archives/C03S1KBA2/p1687196467018599: > Is there a way to do this? >
(def [a b c] [1 2 3])
> I know I could do
> (def a 1)
> (def b 2)
> (def c 3)
> or
> (def temp-coll [1 2 3])
> (def a (nth temp-col 0))
> (def b (nth temp-col 1))
> (def c (nth temp-col 2))
> or something clever with "binding"
Og jeg reagerer i både med "oi, her kan vi bruke makroer!!!" og "er dette virkelig noe du ønsker å gjøre, det virker litt rart?". Så jeg ender opp med å skrive en greie:
(defn defmany* [syms bodies]
`(do
~@(map (fn [sym body]
`(def ~sym ~body))
syms bodies)))
(defmacro defmany [syms bodies]
(defmany* syms bodies))
(macroexpand-1
'(defmany [a b c] [1 2 3]))
;; => (do (def a 1) (def b 2) (def c 3))
... men jeg deler den ikke fordi dette ikke er noe jeg ville sjekket inn i min egen kode. Gøy å lage ting, men dumt å lage ting som bare lager trøbbel.Konteksten for spørsmålet var REPL-poking, så vidt eg forstod... Så det bør ikkje sjekkast inn i/på classpath. Kor praktisk det er med ein slik sak, tja, ikkje veit eg.. Eg snappa opp følgande på #clojure ei stund tilbake:
(ns user)
(ns clojure.core)
(defmacro def-let
"like let, but binds the expressions globally."
[bindings & more]
(let [let-expr (macroexpand `(let ~bindings))
names-values (partition 2 (second let-expr))
defs (map #(cons 'def %) names-values)]
(concat (list 'do) defs more)))
(defn pp [x]
((resolve 'clojure.pprint/pprint) x)
x)
det ligger i dev/user.clj
og vert automatisk lasta når ein starter opp eit REPL (gitt det er på classpath). Då får ein def-let
og pp
(automatisk) tilgjengeleg i alle namespace.
Einig i at makroer bør brukast sparsomleg, og glad over at andre her ser ut til å meina det same.
https://github.com/ptaoussanis/tufte har defnp
, som eg synest er heilt okki iallefall. clj-kondo
har støtte for linting av ting som "liknar på" defn, let, osb.
Eg lagde ein in-house makro inspirert av det, defnl
, der l
står for logged, og eksponerte feilstatistikken ut i ein helsesjekk. Og det igjen gjorde at eg såg ein deploy som hadde gått grønt (med nytt sertifikat), men som no feila. Eg burde vel sjekka loggen, men det er langt raskare (for meg iallefall), å sjekka helseendpointet. Dette igjen (puh) gjorde at eg byrja å skrive eit bibliotek for det: https://github.com/ivarref/defnlogged...
Og så googla eg alternativer litt seint, og såg at det meste allereie finst 🙃. Usikker på om det som finst støtter nøyaktig det same, men anyways...
Oh well. Så kanskje eg berre kaster/dropper det biblioteket:
for custom-made makroer på classpath i prod er ein uting (?)
</wall-of-text>
> konteksten var REPL-poking > Jaaa, sant. Da gir det jo mer mening, jeg ønsker å sjekke hva mellomverdiene var.
En ting jeg har stusset litt på. Nå gjør jeg dette default varier på parametere:
(defn foo
([]
(foo 42))
([n]
baz (n)))
Finnes det andre måter å gjøre det på?
Noen språk lar en gjøre det direkte i signaturen til funksjonen, så det blir noe mindre kode. F.eks. i C#:
public void Foo(int bar = 42)
{
Baz(bar);
}
Eller Python:
def foo(bar = 42):
baz(bar)
Ja, du kan bruke :keys
-destructuring med default parametere, men foreslår at du får det meste styrer unna
fancy destructuring er en fin måte å skrive kode som er vanskelig å lese ja 😅 Ofte foretrekker jeg å bare fiske ut defaults i en let-block eller noe sånt
litt usikker på hva :or
betyr. Vil tro den bare binder om nøkkelen ikke eksisterer i mappet? Eller vil den også binde om verdien er satt til nil i mappet? 😅
Det stemmer. Har gått på den feilen en gang 😅
(defn a [{:keys [foo] :or {foo false}}]
foo)
=> #'user/a
(a {:foo nil})
=> nil
(a {})
=> false
Jeg er ganske fornøyd med å ha fått dette til å funke i dag:
(defn find-in-file [file-path pattern]
(with-open [reader (io/reader file-path)]
(->> (line-seq reader)
(keep-indexed #(when (str/includes? %2 pattern)
{:path file-path
:line (inc %1)
:column (inc (.indexOf (str %2) pattern))}))
(into []))))
Den søker etter et pattern i en fil, og returnerer maps med path, linje og kolonne for lokasjon der pattern matchet.
For eksempel, brukes slik for å finne alle mikrotjenester som bruker en gitt versjon av .NET:
(->> (find-files (get-repo-root-path) ["csproj"])
(map str)
(pmap #(find-in-file % "netcoreapp3.1"))
(remove empty?))
Returnerer eksempelvis:
[{:file-path
"/Users/leif/Code/foo.csproj",
:line 4,
:column 22}]
[{:file-path
"/Users/leif/Code/bar.csproj",
:line 4,
:column 22}]
[{:file-path
"/Users/leif/Code/baz.csproj",
:line 4,
:column 22}]
...)
De fleste .csproj
filer har lik struktur. Derfor er :line
og :column
det samme.
Dette var den første versjonen av funksjonen (kopiert rått fra ChatGPT vel å merke), som også fungerte, men var blodstøgg:
(defn find-in-file [file-path pattern]
(with-open [reader (io/reader file-path)]
(let [lines (line-seq reader)]
(loop [line-num 1
remaining-lines lines
results []]
(if-let [line (first remaining-lines)]
(let [index (.indexOf line pattern)]
(if (not= -1 index)
(recur (inc line-num) (rest remaining-lines)
(conj results {:file-path file-path
:line line-num
:column (inc index)}))
(recur (inc line-num) (rest remaining-lines) results)))
results)))))
man klarer stort sett å komme seg unna loop
ja! Utrolig hva som finnes i core fra før
Yeah! Og dette kjører typ 10x raskere enn Bash scriptet jeg hadde, antagelig pga. pmap
Indeed! Babashka er helt rått. Det har sikkert tatt meg 10x lengre tid å oppnå sluttresultatet jeg prøver å oppnå (😳), men nå lærer jeg litt Clojure samtidig og får noen utils som er enklere å gjenbruke for lignende oppgaver i fremtiden 😁
Pluss at kollegaer går forbi, ser på skjermen min og spør "hva i all verden er det der?!" 😂 Fin anledning til å vise litt!
Så for å ta tak i denne problematikken, fordi den kommer til å bite deg. I gamle dager, var Hibernate kult. Hibernate hadde lazy loading av relasjoner, som også var kult, men som også var tidenes foot gun. Et av problemene var at du måtte ha en åpen connection til databasen når man realiserte lazyness’en. Enter open-session-in-View
eller deromkring. Grunnen til at jeg tar det opp er at vi treffer det samme problemet i Clojure med lazy seqs. I koden over bruker du with-open-et-eller-annet
. Når du er ferdig med den (leksikalske) bindingen er filen din lukket. Hvis du d prøver å realisere seq’en din, går ting normalt til helvete.
En annen artig felle der er hvis du bruker with-redefs
og returnerer en lazy seq, så vil redef’en ikke være synlig når den realiseres. Har tapt litt tid på akkurat det.
nesten så core kunne hatt with-open2 og with-redefs2 som sjekket om du returnerte en LazySeq og automatisk realiserte den for deg som en del av “pakka”
Jeg setter pris på innspillene deres! Det er den beste måten å lære på når folk peker ut dumme ting jeg gjør.
..og dumme ting Clojure gjør :S Ble sittende å tenke på hvordan Pinnacle Of Dev Experience next.js 100% hadde hatt en linter eller runtime-sjekk eller bare automatisk realiserte lazy-seqs i with-open osv. Clojure har liksom bestemt seg for å ikke være sånn “lett” å ha med å gjøre, men heller la foot gunsa ligge
Er dette litt bedre? :thinking_face:
(defn find-in-file [file-path pattern]
(->> (io/file file-path)
(io/reader)
(line-seq)
(map-indexed #(when (str/includes? %2 pattern)
{:path file-path
:line (inc %1)
:column (inc (.indexOf %2 pattern))}))
(filter identity)))
Kanskje enda litt bedre 🙂
(defn find-in-file [file-path pattern]
(->> file-path
(file/read-all-lines)
(map-indexed #(when (str/includes? %2 pattern)
{:directory (str (file/parent file-path))
:filename (file/file-name file-path)
:pattern pattern
:line (inc %1)
:column (inc (.indexOf %2 pattern))}))
(filter identity)))
så om du leser ei svær fil så trenger du ikke laste inn hele fila i RAM først, den kan streames inn mens den prosesseres
Konteksten for spørsmålet var REPL-poking, så vidt eg forstod... Så det bør ikkje sjekkast inn i/på classpath. Kor praktisk det er med ein slik sak, tja, ikkje veit eg.. Eg snappa opp følgande på #clojure ei stund tilbake:
(ns user)
(ns clojure.core)
(defmacro def-let
"like let, but binds the expressions globally."
[bindings & more]
(let [let-expr (macroexpand `(let ~bindings))
names-values (partition 2 (second let-expr))
defs (map #(cons 'def %) names-values)]
(concat (list 'do) defs more)))
(defn pp [x]
((resolve 'clojure.pprint/pprint) x)
x)
det ligger i dev/user.clj
og vert automatisk lasta når ein starter opp eit REPL (gitt det er på classpath). Då får ein def-let
og pp
(automatisk) tilgjengeleg i alle namespace.
Einig i at makroer bør brukast sparsomleg, og glad over at andre her ser ut til å meina det same.
https://github.com/ptaoussanis/tufte har defnp
, som eg synest er heilt okki iallefall. clj-kondo
har støtte for linting av ting som "liknar på" defn, let, osb.
Eg lagde ein in-house makro inspirert av det, defnl
, der l
står for logged, og eksponerte feilstatistikken ut i ein helsesjekk. Og det igjen gjorde at eg såg ein deploy som hadde gått grønt (med nytt sertifikat), men som no feila. Eg burde vel sjekka loggen, men det er langt raskare (for meg iallefall), å sjekka helseendpointet. Dette igjen (puh) gjorde at eg byrja å skrive eit bibliotek for det: https://github.com/ivarref/defnlogged...
Og så googla eg alternativer litt seint, og såg at det meste allereie finst 🙃. Usikker på om det som finst støtter nøyaktig det same, men anyways...
Oh well. Så kanskje eg berre kaster/dropper det biblioteket:
for custom-made makroer på classpath i prod er ein uting (?)
</wall-of-text>