This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-03-11
Channels
- # announcements (1)
- # aws (5)
- # beginners (35)
- # calva (18)
- # clerk (5)
- # clojure (20)
- # clojure-berlin (1)
- # clojure-dev (12)
- # clojure-europe (16)
- # clojure-nl (1)
- # clojure-norway (159)
- # clojure-uk (5)
- # clojurescript (8)
- # conjure (1)
- # cursive (18)
- # events (10)
- # fulcro (23)
- # hyperfiddle (5)
- # introduce-yourself (3)
- # juxt (2)
- # off-topic (1)
- # polylith (4)
- # portal (11)
- # releases (1)
- # shadow-cljs (4)
- # xtdb (9)
- # yamlscript (1)
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?
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/
greit verktøy det (av og til)
> 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?
> 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!
@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.
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.
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.
Det er nesten like lett å ha ting på disk, du må bare laste ned transactoren en gang 🙂
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.
@teodorlu her er nedlasting og start av transactor: https://github.com/Mattilsynet/matvaretabellen-deux/blob/main/Makefile#L11
Bare endre ../../datomic-data
til der du vil at Datomic skal lagre på disk, og så er du good to go
TIL: “deux” betyr “to” på mellomfransk. Ikke fransk, ikke gammelfransk, mellomfransk.
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.
korreksjon: ser ut som det funker på fransk også! Bakoverkomtatibelt, sånt liker vi.
Ja, jeg har også hatt Datomic slengende tidligere. Fint at alt som kreves for å jobbe med prosjektet er fullt spesifisert av configen i prosjektet.
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”
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)
Vi bruker ikke miljøvariabler til noenting akkurat nå. Har ikke pleid å gjøre meg avhengig av det.
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
sunt. Så ting krasjer med et smell hvis noen (ved et uhell) prøver å kjøre appen i prod uten å sette noe config?
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
Sender en liten varsel om mye koselig prat lettere skjult i denne tråden til resten av gjengen 😅
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.
Alternativt kan man annotere selve transaksjonen med git-shaen, da får du en enda mer direkte kobling.
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. 😅
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}
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 😄
(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.)
Hvis all koden er perfekt, så er den enten veldig liten, eller du har gjort feil trade-off en eller annen plass. 😅
Jeg tror jeg ville kommet trekkende med gode, fokuserte, bivirkningsfrie funksjoner som opererte på strukturene du hadde i treet ditt.
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
.
In other news. Detaljene i javascripts async/await får meg til å synes at core.async er en cakewalk.
@teodorlu de tre dekker det meste. Men jeg skyter inn assoc-in
og update-in
som du helt sikkert også bruker 🙂
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?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.
callback hell er på en måte bra, for det det er så opp i trynet ditt åpenbart at du gjør ting feil.
const foo = async (whatever) => return await fetch(...);
Dette blir da:
const foo = async (whatever) => return new Promise((resolve, reject) => {
fetch().then(resolve, reject);
});
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
Eksempelet over er da basically det samme som:
const foo = async (whatever) => fetch(...);
Så jeg må resolve promiset som fetch
returnerer for å få fanget exceptionen i den blokka?
Sånn ca https://dev.to/dionarodrigues/fetch-api-do-you-really-know-how-to-handle-errors-2gj0
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
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);
}
});
}
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.
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.
Man kunne også kanskje argumentere for at api’et til fetch
er dust, i og med at det blander sammen promises og exceptions.
Hvis du får en nettverks- eller corsfeil, så kaster den en exception, pr blog over her i tråden.
const foo = async (whatever) => fetch(...);
ville returnert et Promise<Promise<Response>>
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)
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.
Jeg tror du ville ha godt av å skrive denne koden med Promise-API-et (uten syntaks) frem til det sitter
async/await er kun syntaks, det er promises som er mekanismen. Dette blir lettere med en god forståelse av promises.
> 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.
tipper det er svært få som bruker promises lenger, så det som blir nevnt her er fort gjort å ikke få med seg 😓
Du mener at sensei @christian767 er utdatert og sitter i hagen sin og roper på skyene?
Hvis du skriver om koden fra async/await til eksplisitte promises så får du lov å uttrykke deg med callbacks @U04V5VAUN 😄
const foo = (whatever, onError, onSuccess, onFetchThrows) => { try { fetch(...).then(onSucces).error(onError) } catch (e) { onFetchThrows(e)}}
Hver gang du kommer med en sånn oneliner må jeg copy-paste den til emacs og formattere den for å klare å lese den 😅
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
Blir det bedre for deg hvis jeg skriver:
function foo(whatever, onError, onSuccess, onFetchThrows) {
....
}
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.
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.
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.
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.
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 😁 😅
> 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?
> 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.
https://clojurians.slack.com/archives/C03S1KBA2/p1710139286844469 noen her som har meninger om dette?
kanskje du kan lage en “hook” som brukere kan bruke som de vil, og gjøre eventuelle kall til tap>
selv?
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.
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.
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/
Altså fra "den logger interessante warnings og feil" til "den kommuniserer ut warnings, og har en valgfri default logger for dem"
Sender en liten varsel om mye koselig prat lettere skjult i denne tråden til resten av gjengen 😅
Langhelg i Belgia over sommeren? Heart of Clojure blir 18. og 19. september i Leuven. https://clojurians.slack.com/archives/CBJ5CGE0G/p1710171645118839
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.