Fork me on GitHub
#clojure-norway
<
2024-03-11
>
teodorlu07:03:48

God morgen :)

teodorlu07:03:29

Er det andre verktøy jeg bør ha i verktøykassa mi for å transformere trær enn • loop/recur • tree-seq • clojure.walk/postwalk ? Hva bruker dere i praksis?

👀 1
magnars09:03:23

Det kommer jo veldig an på størrelsen av problemet, men en av mine favorittmåter å flate ut data er ved å dytte dem inn i Datomic. Ref https://parenteser.mattilsynet.io/flate-data/

👍 1
Ivar Refsdal09:03:49

greit verktøy det (av og til)

teodorlu11:03:22

> Datomic jaaaaaaa. Sant! Dette mener jeg dere allere har snakket om, lurer på om dere nevnte “Putt det i Datomic for å gjøre grafspørringer” på Clojure-meetupen i fjor sommer. Og dere gjør vel allerede dette på http://kodemaker.no og på den statiske nettsiden dere har laget i mattilsynet. Jeg prøvde Datomic såvidt forrige uke; det gikk mye kjappere å komme i gang med com.datomic/peer {:mvn/version "1.0.7075"} og datomic:mem:// enn jeg trodde. Nesten så man ikke trenger Datascript hvis man uansett er på en JVM?

👍 1
teodorlu11:03:00

> Zippers Godt poeng. Det har jeg aldri fått brukt “in anger”. God unnskyldning til å endelig lære seg. Takk for reveransen. Har lest den før, mulig jeg må lese en gang til!

magnars14:03:12

@christian767 og jeg satt nylig og jobbet med en hel del data, som bare skulle være midlertidig. Det var litt styrete, men gikk helt greit. Dagen etter hadde Christian slengt dataene inn i en midlertidig Datomic database istedenfor. Det var hinsides mye bedre. Ikke bare jobbet vi raskere, men det var så mye lettere å få til ting at vi også testet flere teorier, og gravde i ting vi ikke ellers ville tatt oss tid til.

👍 1
teodorlu15:03:33

kjørte dere den databasen i-minnet, eller gikk dere for persistens av noe slag? Mulig jeg kommer til å prøve noe selv, og er litt usikker på akkurat den biten. Tenker at i-minne kanskje er greit hvis man lett klarer å reprodusere det man skal lokalt. Så kan man eventuelt serialisere til filer igjen fra datomic hvis man vil det.

cjohansen15:03:55

Starta med data i minnet, og så flytta vi det til disk fordi det tar 3-4 minutter å pumpe data inn i databasen. Greit å slippe det hver gang du starter REPL-et.

👍 1
cjohansen15:03:14

Det er nesten like lett å ha ting på disk, du må bare laste ned transactoren en gang 🙂

👍 1
teodorlu15:03:30

Nice, skjønner! Ja, det blir jo mye mer REPL-vennlig å kunne gjøre spørringer med én gang enn å først måtte kverne data i flere minutter.

cjohansen15:03:09

Det kommer an på mengden. in-memory er helt fint til det ikke er det 😄

cjohansen15:03:57

Bare endre ../../datomic-data til der du vil at Datomic skal lagre på disk, og så er du good to go

teodorlu15:03:24

ultradigg at dette er open source ❤️

cjohansen15:03:32

Staten ❤️

💯 1
teodorlu15:03:52

sånn statlig finansiert utvikling og forskning bør gjøres!

😄 1
teodorlu15:03:39

TIL: “deux” betyr “to” på mellomfransk. Ikke fransk, ikke gammelfransk, mellomfransk.

😂 1
teodorlu15:03:13

datomic-config direkte i en makefile var en veldig god idé_!_ Tidligere har jeg bare puttet Datomic et sted, som jeg synes har vært litt … lite oversiktelig.

💡 1
cjohansen15:03:54

Jeg trodde det var fransk, så lite kan jeg om det språket 😄

teodorlu15:03:23

korreksjon: ser ut som det funker på fransk også! Bakoverkomtatibelt, sånt liker vi.

😁 2
cjohansen15:03:29

Ja, jeg har også hatt Datomic slengende tidligere. Fint at alt som kreves for å jobbe med prosjektet er fullt spesifisert av configen i prosjektet.

👍 1
teodorlu15:03:08

ja, jeg liker det veldig godt jeg også. Ikke “også må du sette disse 1546 miljøvariablene i intellij og starte databasen før du trykker på “start”-knappen”

cjohansen15:03:27

Det er ingen intellij-hjelp for prosjektene våre 😂

😄 1
cjohansen15:03:51

Men ja, enig

teodorlu15:03:17

setter dere miljøvariabler når dere jobber lokalt? Eller sørger dere for at variabler har defaults (i appen) som funker i lokal utvikling? (edit: dette kan jeg sikkert lese meg til, dere har jo koden åpent)

cjohansen15:03:58

Vi bruker ikke miljøvariabler til noenting akkurat nå. Har ikke pleid å gjøre meg avhengig av det.

👍 1
magnars15:03:00

config-opplegget vårt er behørig beskrevet i confair-README 😅

👍 1
cjohansen15:03:07

hehe, riktig 😄

magnars15:03:08

kort fortalt: Alt skal funke ut av boksen lokalt

💯 2
cjohansen15:03:22

Launchpad hadde en ganske kul greie for env-variabler

cjohansen15:03:29

Men confair er "the way"

👍 1
magnars15:03:26

Litt lengre forklart: • alt av dev-config sjekket inn og delt • mulighet til å overstyre lokalt, i en fil som ikke sjekkes inn • prod-config og dev-config er 100% adskilt - ingen gjenbruk

💯 1
teodorlu15:03:27

sunt. Så ting krasjer med et smell hvis noen (ved et uhell) prøver å kjøre appen i prod uten å sette noe config?

magnars15:03:35

nettopp det

👍 1
cjohansen15:03:58

env-variabler brukes kun til å fore inn ting som må komme runtime i prod. Vi har brukt det til git sha og hemmeligheten som dekrypterer configen

👍 1
teodorlu15:03:09

liker illustrasjonsbildet i confair 🙂

😅 1
magnars15:03:58

Sender en liten varsel om mye koselig prat lettere skjult i denne tråden til resten av gjengen 😅

3
👍 1
teodorlu15:03:24

tråden anbefales hardt av undertegnede ☝️

cjohansen15:03:58

Forøvrig, kult protip: Vi har tidligere transacta git-shaen på koden til Datomic ved oppstart i prod. Da kan du lett se hvilken versjon av koden som har opprettet ethvert stykke data i databasen.

💯 1
cjohansen15:03:41

Alternativt kan man annotere selve transaksjonen med git-shaen, da får du en enda mer direkte kobling.

👍 2
cjohansen15:03:45

Apropos ingenting.

magnars15:03:15

det er litt utsatt for timing-issues når man kjører flere app-prosesser, men vil gi et inntrykk i hvert fall. Løsningen på det har Christian allerede skrevet ut. 😅

👍 1
teodorlu15:03:21

jeg har med meg git-sha i prod på en app jeg har jobbet på selv, supernyttig å kunne se når nye versjoner har startet opp. Per nå er denne fra en miljøvariabel fordi det er noe plattformen tilbyr.

$ curl 
{:git/sha "a00b84b815c214ecb2b586732c0c824c3035e722",
 :last-modified-file-time "2024-03-05T10:55:42Z",
 :access-logs-size "46 MB",
 :hits 508796}

magnars15:03:05

"mikrobloggeriet" 👌 👌

❤️ 1
😸 1
teodorlu15:03:31

Navnet ble funnet av https://www.linkedin.com/in/hmkristiansen/, en tidligere kollega. Fram til det tidspunktet het initiativet OLORM (“Oddmund, Lars og Richard sin Mikroblogg”). Det navnet kan jeg love deg at folk ikke likte 😄

😅 1
😂 1
teodorlu15:03:27

(hvis dere skulle finne på å lese koden (ikke gjør det da 😅) vil dere sikkert observere at vi gjør litt manuell jobb for å holde styr på ting folk har publisert og grupper for å publisere ting. Det hadde blitt enklere med feks en database som Datomic. Siden hadde også blitt kjappere med flate filer. Men da kunne vi ikke brukt Mikrobloggeriet som treningsarena for utviklere som vil prøve Clojure. Og vi kunne heller ikke flagget features på måten vi gjør det nå. Mye som kan forbedres for det trente øyet.)

cjohansen15:03:41

Slapp av, det meste jeg gjør kunne også vært gjort bedre 😊

teodorlu15:03:40

næh, det nekter jeg å tro på! 😁 </sarcasm>

magnars16:03:39

Hvis all koden er perfekt, så er den enten veldig liten, eller du har gjort feil trade-off en eller annen plass. 😅

1
👍 2
😁 2
slipset07:03:32

Noen vil sikkert komme trekkende med specter ,men jeg ville ikke gjort det.

🔥 1
slipset07:03:28

Jeg tror jeg ville kommet trekkende med gode, fokuserte, bivirkningsfrie funksjoner som opererte på strukturene du hadde i treet ditt.

👍 1
teodorlu17:03:06

egentlig veldig kult hvordan tree-seq går akkurat dette veien. Hvis du gir meg (A) en trestruktur, (B) en måte å avgjøre om det jeg gir deg er en node eller et blad og (C) en måte å avgjøre alle trær, så kan jeg gi deg en måte å traversere over tingen din. Hvis du skriver (B) og (C) som funksjoner, kan du bruke dem selv, eller bruke dem i tree-seq.

slipset17:03:59

funksjonskonposisjon, eller som noen ville sagt, funksjonell programmering

😁 1
slipset07:03:05

In other news. Detaljene i javascripts async/await får meg til å synes at core.async er en cakewalk.

😄 1
cjohansen07:03:41

specter er en elegant løsning på feil problem 😅

😄 3
cjohansen07:03:03

@teodorlu de tre dekker det meste. Men jeg skyter inn assoc-in og update-in som du helt sikkert også bruker 🙂

👍 1
slipset07:03:52

Sånn lissom:

const foo = async (whatever) => return await fetch(...);
Hva er retur typen til foo
const foo = async (whatever) => return fetch(...)
Hva er retur typen til foo
const foo = async (whatever) => {try { return await fetch(...) } catch (e) { will this code ever be executed }}
Når blir catch blokka eksekvert?
const foo = async (whatever) => {try { return fetch(...) } catch (e) { will this code ever be executed }}
Når blir catch blokka eksekvert?

1
cjohansen07:03:39

async/await er vel bare syntaks-sukker for promises?

slipset07:03:54

Jepp. Og der jeg aldri har jobbet med promises, fordi jeg slutta med JS før de kom, synes jeg at async/await prøver å gjøre noe som er vanskelig enkelt, men leverer en gjeng med foot guns istedet.

slipset07:03:32

callback hell er på en måte bra, for det det er så opp i trynet ditt åpenbart at du gjør ting feil.

cjohansen07:03:33

const foo = async (whatever) => return await fetch(...); Dette blir da:

const foo = async (whatever) => return new Promise((resolve, reject) => {
  fetch().then(resolve, reject);
});

cjohansen07:03:26

Jeg drister meg til å påstå at denne forvirringen først og fremst handler om at du ikke er like kjent med promises som callbacks og core.async

1
slipset07:03:53

Behøver ikke driste deg til å påstå det. Jeg vil påstå at det er helt korrekt!

cjohansen07:03:07

Eksempelet over er da basically det samme som:

const foo = async (whatever) => fetch(...);

slipset07:03:39

Jo, men! Jeg vil fange exceptions som fetch kaster i foo som i eksemplet lenger ned.

slipset07:03:07

Så jeg må resolve promiset som fetch returnerer for å få fanget exceptionen i den blokka?

cjohansen07:03:38

Dette eksempelet fungerer nok ikke som ønsket:

const foo = async (whatever) => {
  try {
    return fetch(...)
  } catch (e) {
    will this code ever be executed
  }
}
try/catch vil kun ta exceptions som oppstår synkront i fetch

cjohansen07:03:31

Mens dette:

const foo = async (whatever) => {
  try {
    return await fetch(...)
  } catch (e) {
    will this code ever be executed
  }
}
Vel er det samme som:
const foo = async (whatever) => {
  return new Promise((resolve, reject) => {
    try {
      fetch(...).then(resolve).catch(reject);
    } catch (e) {
      // sync exceptions
      return Promise.reject(e);
    }
  });
}

cjohansen07:03:58

(Jeg er ikke ekspert på async/await syntaks, men jeg har brunt belte i promises)

slipset07:03:32

Jeg har sånn omtrent akkurat kjøpt meg promise-drakt, ha’kke fått belte ennå.

cjohansen07:03:57

Anbefaler et lite dedikert dypdykk i promises uten syntaks

slipset07:03:15

Men jeg tror jeg hadde kanskje gult belte i callbacks for ti år siden.

cjohansen07:03:39

Jeg er usikker på om jeg syns syntaksen i async/await er en nett positiv

cjohansen07:03:49

Den piggie backer eksisterende syntaks med ny semantikk

slipset07:03:36

Det er vel det som er observasjonen min. Det tar noe som krever at du har tunga rett i munnen, men som funker og får det til å se ut som noe som du ikke behøver å tenke så mye på. Hvilket er feil.

💯 2
slipset07:03:57

Forskjellen på den await ’en etter return har masse å si, men det er ingen ting som indikerer det. Typene forblir de samme, og verken kompilatoren eller linteren gir deg noe hint om at du ikke gjør det du tror du gjør.

cjohansen07:03:02

Tror egentlig at det er ålreit, men try/catch blir plutselig ugrei

cjohansen07:03:40

Jøss, sier du at typesystemet ikke garanterer ar programmet ditt virker? trollface

slipset07:03:15

Man kunne også kanskje argumentere for at api’et til fetch er dust, i og med at det blander sammen promises og exceptions.

cjohansen07:03:09

Wow. Har fetch synkrone exceptions også?

slipset07:03:32

Såvidt jeg kan forstå, så ja.

slipset07:03:08

Hvis du får en nettverks- eller corsfeil, så kaster den en exception, pr blog over her i tråden.

cjohansen07:03:48

Lul. Det er klassisk w3c, de er ikke så gode på API-design

slipset07:03:52

Jeg tror forøvrig at jeg hadde forventet at

slipset07:03:50

const foo = async (whatever) => fetch(...);
ville returnert et Promise<Promise<Response>>

cjohansen07:03:39

Nei, det er hele magien med Promises

cjohansen07:03:54

Det er alltid Promise<Response|Error>

cjohansen07:03:07

(jeg har ikke typesignatur-belte, så tilgi syntaks)

slipset07:03:41

Så promises kollapser lissom?

cjohansen07:03:28

Ellers hadde det blitt umulig å jobbe med

cjohansen07:03:34

then er som <! 😄

cjohansen07:03:30

Altså, det er forsåvidt mulig å kalle resolve med et promise, da må du gjøre then to ganger. Men det er omtrent like fornufig som å legge en kanal på en kanal med (<! chan other-chan)

slipset07:03:57

Fikk anbefalt denne https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ > I just jammed a bunch of fancy PL language in that paragraph so it probably sounds like a sweet deal, but it’s basically snake oil. Kommer noen morsomme quotes litt nedi der.

cjohansen07:03:03

Jeg tror du ville ha godt av å skrive denne koden med Promise-API-et (uten syntaks) frem til det sitter

slipset07:03:28

Eller til og med callbacks. Men ja. jeg tror det hadde vært gunstig.

cjohansen07:03:23

async/await er kun syntaks, det er promises som er mekanismen. Dette blir lettere med en god forståelse av promises.

cjohansen07:03:34

Jeg er heldig, for jeg lærte meg promises før det fantes syntaks for dem 😄

slipset07:03:20

> Wanna know one that doesn’t? Java. I know right? How often do you get to say, “Yeah, Java is the one that really does this right.“? But there you go. In their defense, they are actively trying to correct this oversight by moving to futures and async IO. It’s like a race to the bottom.

odinodin08:03:15

tipper det er svært få som bruker promises lenger, så det som blir nevnt her er fort gjort å ikke få med seg 😓

slipset08:03:58

Du mener at sensei @christian767 er utdatert og sitter i hagen sin og roper på skyene?

😂 1
cjohansen08:03:58

De bruker det, men er ikke klar over det

cjohansen08:03:06

Jeg er utdatert som faen

cjohansen08:03:16

Men jeg skjønner ihvertfall hva som skjer trollface

cjohansen08:03:49

Hvis du skriver om koden fra async/await til eksplisitte promises så får du lov å uttrykke deg med callbacks @U04V5VAUN 😄

slipset08:03:30

Hvis bare fetch hadde hatt callbacks 😕

cjohansen08:03:08

fetch().then(function (res) { voila! });

slipset08:03:20

const foo = (whatever, onError, onSuccess, onFetchThrows) => { try { fetch(...).then(onSucces).error(onError) } catch (e) {  onFetchThrows(e)}}

cjohansen08:03:19

Hver gang du kommer med en sånn oneliner må jeg copy-paste den til emacs og formattere den for å klare å lese den 😅

slipset08:03:37

Du er gammel

cjohansen08:03:57

Jeg mente ikke nødvendigvis at du skulle gå så all-in på callbacks, men at new Promise(function (resolve, reject) { ... }) er litt mer callback-orientert enn await

slipset08:03:07

Blir det bedre for deg hvis jeg skriver:

function foo(whatever, onError, onSuccess, onFetchThrows) {
....
}

cjohansen08:03:35

Jeg har mest problem med try/catch på én linje

slipset08:03:52

Ja, det er helt håpløst.

cjohansen08:03:02

Det ville jeg ikke gjort i Clojure engang

slipset08:03:12

Det er artig å lese kommentarene i den blogposten jeg lenket til. Blogposten ender med a si at threads er en enklere programmeringsmodell (noe jeg vel er veldig enig i). Kommentarfeltet blir da fullt av “Jammen threads skalerer jo ikke. Kanke ha millllioner av threads lissom”. Og da ler jeg litt.

teodorlu07:03:37

assoc-in og update-in er 😘!

teodorlu07:03:29

async+exceptions er ... vanskelig I hodet mitt i alle fall.

slipset07:03:37

Det man skal passe seg for med assoc-in og update-in er når path vektoren blir for lang/komplisert. Da er man fort inne i specter land, og der vil man ikke være. Husk Demeter.

👍 1
cjohansen07:03:07

Sier du at flate data er bedre enn nøsta data? :thinking_face:

😄 1
1
😁 1
slipset07:03:24

Jepp, leste det på en blogg et sted. Virka fornuftig.

🔥 1
❤️ 1
slipset07:03:53

En eller annen blogg om bananer tror jeg.

😂 3
🍌 1
teodorlu07:03:53

Haskell har: • Alt som lazy som default • Og exceptions. Jeg skjønner fremdeles ikke helt hvordan de to skal funke sammen. Du risikerer å få en "thunk" (data som ikke har blitt laget ennå), men som eksploderer når du prøver å evaluere (exception). Exceptions brukes feks når du prøver å lese en fil som ikke finnes, eller hvis du henter ut første element av en tom liste.

teodorlu07:03:25

Du kan jo forsåvidt også få noe lazy i clojure so eksploderer når du prøver å evaluere? Det er kanskje et argument for å kun bruke lazyness sammen med rene funksjoner.

cjohansen07:03:17

Da mener du at en funksjon som kaster exception ikke er ren?

cjohansen07:03:05

Lazy exceptions er den aller største kilden til frustrasjon i Clojure tror jeg. Det er ikke ofte jeg er utfor det, men det er veldig ubehagelig når det skjer 😁 😅

😄 1
teodorlu09:03:11

> Da mener du at en funksjon som kaster exception ikke er ren? Hmm. Ja, det var antagelsen. Hva tenker du? Kan en ren funksjon kaste exceptions?

teodorlu09:03:37

> Lazy exceptions er den aller største kilden til frustrasjon i Clojure tror jeg. Det er ikke ofte jeg er utfor det, men det er veldig ubehagelig når det skjer 😁 😅 Jeg tenkte over det da du kom med et pmap-eksempel som gjorde HTTP-spørringer her om dagen (tror jeg).

user=> (doc pmap)
-------------------------
clojure.core/pmap
([f coll] [f coll & colls])
  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.

teodorlu09:03:24

kanskje du kan lage en “hook” som brukere kan bruke som de vil, og gjøre eventuelle kall til tap> selv?

cjohansen09:03:57

Ja, det blir nok det

teodorlu09:03:20

Jeg liker egentlig litt at det ikke er fullt av vilkårlige ting på data som sendes til tap>. Men jeg har ikke brukt tap> kjempemye selv. Clerk har en tap>-viewer: https://book.clerk.vision/#tap-inspector Som jeg brukte til å logge noe data basert på HTTP-spørringer. Den tap-vieweren baserer seg på tilstand i et atom. Så har de en funksjon for å “tømme” det atom-et.

cjohansen09:03:16

Helt enig. Men jeg har heller ikke brukt tap så mye som jeg sikkert burde, så var litt usikker på hva som var gjengs. Men jeg er nå helt overbevist om at applikasjonsutvikleren bør ha full kontroll over hva som går på tap.

👍 1
teodorlu09:03:44

Det høres jo uansett ut som et trygt valg for meg. har du lest dette skikkelig kule blogginnlegget fra 2021? https://www.kodemaker.no/blogg/2021-11-mer-mindre/

cjohansen09:03:56

Idéen var jo å fjerne den konkrete loggingen fra featuren

👍 1
cjohansen09:03:29

Altså fra "den logger interessante warnings og feil" til "den kommuniserer ut warnings, og har en valgfri default logger for dem"

👍 1
magnars15:03:58

Sender en liten varsel om mye koselig prat lettere skjult i denne tråden til resten av gjengen 😅

3
👍 1
teodorlu16:03:02

Langhelg i Belgia over sommeren? Heart of Clojure blir 18. og 19. september i Leuven. https://clojurians.slack.com/archives/CBJ5CGE0G/p1710171645118839

Zeniten16:03:20

Jeg har lyst, da dette så utsøkt ut!

💯 1
slipset18:03:32

Etter dagens vandringer i the promise land of async/await, har jeg kommet fram til at jeg egentlig ikke liker async/await og som den gamle mannen @christian767 heller foretrekker promises. Men det er kanskje en litt artig grunn til at jeg liker promises. Og det er fordi at man kan skrive funksjoner som opererer på helt vanlig verdier, og så kan man slenge dem inn i en sånn promise greie. Problemet er at “alle” tutorials skriver kode a la:

const foo = (p:Promise) => {
   return p
     .then(whatever => { ... dustete lang anon-fn})
     .catch(error => { dustete lang anon-fn})
};
men! Dette burde burde jo heller skrives som:
const something = (w:Whatever) => { dustete lang fn};

const thingsBlewUp = e => { dustete lang error handler };

const foo = (p:Promise) => p.then(something).catch(thingsBlewUp)
Dette er sikkert gammelt nytt for mange. Det fine er jo at akkurat som vanlig, så får du nå separert domenelogikken din fra cruftet.

👌 1
slipset19:03:40

👋

👋 4