Fork me on GitHub
#clojure-norway
<
2024-02-02
>
slipset11:02:15

Av og til glemmer jeg litt hvor fantastisk immutability er. Fra greiene over, så gjør vi ting som

(baz (assoc ctx :foo bar))

slipset11:02:22

Dette kan vi gjøre helt trygt fordi immutability. Ingen, bortsett fra baz og de funksjonene den kaller vil se denne endrede ctx’en. Tenk på dette i en Spring verden. Da må du ha life cyclen til ctx i hodet for å forstå om dette er trygt eller ikke, mest sannsynlig er det ikke det, og du må minst huske på å cleare :foo når kallet til baz er ferdig. Godt med alle de tinga man ikke behøver tenke på.

❤️ 1
Zeniten11:02:48

Det er herlig. :smiling_face_with_3_hearts:

magnars11:02:31

Ja, det er ikke lenge siden jeg satt og beundret akkurat samme situasjon. Jeg fikk et flash fra gamledager av "er dette trygt?" med tilhørende ubehag, helt til jeg skjønte at ... Jeg skriver Clojure nå. Det er helt trygt. 😄

❤️ 1
teodorlu11:02:59

FP er topp!

teodorlu11:02:36

> Dette kan vi gjøre helt trygt fordi immutability. Ingen, bortsett fra baz og de funksjonene den kaller vil se denne endrede ctx’en. > > Tenk på dette i en Spring verden. Da må du ha life cyclen til ctx i hodet for å forstå om dette er trygt eller ikke, mest sannsynlig er det ikke det, og du må minst huske på å cleare :foo når kallet til baz er ferdig. Jeg vil også legge inn et slag for FP+ren data. Du får de samme immutability-garantiene med Haskell. Men du kan ikke bruke assoc og dissoc, fordi du må tenke typer på dataen din. Ref gårsdagens “https://clojurians.slack.com/archives/C061XGG1W/p1706777452109809”. Går ikke i Haskell, fordi assoc og dissoc ikke funker med typene.

slipset13:02:19

Bittelitt uenig i deg her. Vi har modellert ctx omtrent sånn:

(s/def ::ctx (s/keys :req-un [::org
                              ::org-ds
                              ::ardoq-ds
                              ::org-db
                              ::user-db
                              ::common-db
                              ::user]
                     :opt-un [::branch ::scope ::client-request-id :context/provenance ::user-time]))
Det betyr jo i f.eks Haskell-land at du har en Maybe(userTime) eller deromkring (evt en Union type greie). Uansett, vi har definert opp ctx til å ha en del greier som man da også i Haskell-land ville kunne endre på.

slipset13:02:55

Det jeg ikke kunne gjort i Haskell (gitt den definisjonen over) var

(assoc ctx :jeg-bare-trengte-denne-akkurat-nå bla)

slipset14:02:12

(og, for å gi litt credit til Haskell, det er jo f*ckings umulig å vite hva folk assoc ’er på ctx rundt om i kodebasen. Det over er jo muligens kanskje et hint om et subset som kanskje er i bruk 🙂

teodorlu14:02:08

Godt poeng, type for ctx er en mulighet.

teodorlu14:02:50

dere har da et sett med funksjoner som alle tar et ctx-argument først, ala

(f ctx x y z)
?

slipset14:02:07

Sånn ca.

👍 1
slipset14:02:51

Alt som herjer med databasen må ha en ctx parameter.

👍 1
slipset14:02:06

Da vet man at alt som ikke har en ctx ikke herjer med databasen.

👍 1
teodorlu14:02:23

aaah, interessant.

teodorlu14:02:56

ctx har en database-connection også?

slipset14:02:56

Selvom det ikke er helt sant over alt, og det tripper meg hele tiden.

👍 1
slipset14:02:28

Ja, du ser jo det fra spec’en, den har vel en 4 -5 db connections til mongo og pg.

👍 1
slipset14:02:33

Vi har funksjoner som gjør

(defn bla [ctx]
   (fn [lol]
       (repo/query! foo-repo/config ctx pq/all)))
Og da har du plutselig mista oversikten…

slipset14:02:50

Her kunne kanskje Haskell ha hjulpet, i og med at type-systemet ville tracka at du var i impure-land?

teodorlu14:02:55

Jepp, du måtte returnert en IO noe hvis du skulle kunne gjort sideeffekter. (selv om det går an å ødelegge den garantien med unsafePerformIO)

slipset14:02:36

En sånn halvveis artig greie som jeg kunne tenkt meg å leke med når jeg blir pensjonist er hvis du tar funksjonen over og gjør den om til:

(defn bla []
   (fn [lol]
       (make-query foo-repo/config pq/all)))
Dvs at heller enn å utføre effekten, så beskriver du den, og så drar du det ut av beskrivelsesland og inn i bivirkningsland helt på slutten.

teodorlu14:02:02

Mao, typen til bla måtte vært Ctx -> IO Noe, der Noe er typen til resultatet fra (repo/query! ,,,)

slipset14:02:18

Dvs at du tilslutt har noe som:

(run-queries! ctx all-the-collected-queries)

teodorlu14:02:17

Da har du kommet tett på hvordan Haskell funker i praksis! Du kan fint evaluere noe som gir et IO-resultat, men det skjer ikke noe hvis det resultatet ikke “bobles opp” til at IO-effekter skal kjøres.

slipset14:02:01

Det er litt kult hvis man tenker på dette litt (og det hender det at jeg får tid til å gjøre)

slipset14:02:47

Heller enn å kombinere resultatet fra flere queries, kan man kombinere queriene og få et resultat.

👍 1
teodorlu14:02:47

Hva vil motivasjonen være for å “dytte sideeffektene helt til slutten”? Jeg kjøper at kontroll på sideeffekter er nyttig. Men ville du konkret vunnet feks ergonomi fra REPL-en eller testbarhet hvis du hadde gjort det i dag?

slipset14:02:18

Litt avhengig av alt, lissom, men en tur til databasen koster skjorta. Så la oss ta no’ helt gal kode:

(let [record (db/query! db record (by-id 3)]
  (map #(db/query! db song (by-id %)) (:songs record))
Dette er så feil det kan få blitt, men sånn var en del av koden da jeg kom til ardoq, selvom den kanskje egentlig så ut som:
(let [record (record-dao/by-id db 3)]
   (map #(song-dao/by-id db %) (:songs record))
Klassisk n+1 drittkode

teodorlu14:02:44

Jaa, mye bedre ytelse.

slipset14:02:32

Men, det kan også være (og det ser vi) at PG snubler i indeksene sine og at av og til så er det billigere å utføre to spørringer enn det er å konbinere dem.

👍 1
cjohansen14:02:07

Fristende å komme trekkende med datomic igjen 😄

datomic 1
slipset14:02:17

Jepp. men ville tro at man står fritt til å skyte seg selv i foten med Datomic også, selvom det kanskje er vanskeligere enn med Mongo.

slipset14:02:00

(let [record (record-dao/by-id db 3)]
   (map #(song-dao/by-id db %) (:songs record))
Er vel fullt mulig i Datomic også, men det gir kanskje mindre utslag på performance?

teodorlu14:02:05

hvis du kjører spørringer mot en peer som kjører i samme prosess som JVM-koden din, kan du vel gjøre så mange spørringer som du vil uten at det er noe problem?

💯 1
cjohansen14:02:43

Ja, n+1 er ikke en problemstilling i datomic

cjohansen14:02:33

Det var det ene, det andre jeg tenkte på er at les i prinsippet er pure, og side-effekter utføres ved å bygge transaksjoner av data

cjohansen14:02:49

Så den svarte på ønskene lenger opp i tråden 😊

teodorlu14:02:32

> les i prinsippet er pure Så lenge man sender inn en DB-versjon til funksjonen, feks ved å putte en DB-versjon på et ctx-map eller et req-map?

cjohansen14:02:21

En db-verdi, ja

👍 1
teodorlu14:02:45

Jeg synes det er helt utrolig hvor dypt det funksjonelle kaninhullet går. Jeg trodde liksom jeg hadde skjønt det da jeg skrev Haskell for første gang, og kunne stole på hva som skjedde hvor — i kontrast til java-kode jeg hadde skrevet tidligere. Men det har så utrolig mye å si for arkitektur av én app, og design av systemer hvor flere apper skal snakke sammen.

cjohansen14:02:32

Det er mye trygghet i å samle side-effekter på én avgrenset plass ihvertfall.

👍 1