This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-02-09
Channels
- # announcements (4)
- # babashka (25)
- # beginners (11)
- # calva (32)
- # clj-kondo (5)
- # clojure (130)
- # clojure-dev (11)
- # clojure-europe (17)
- # clojure-nl (1)
- # clojure-norway (96)
- # clojure-spec (1)
- # clojure-uk (3)
- # clojurescript (9)
- # conjure (2)
- # cursive (8)
- # datalevin (1)
- # etaoin (14)
- # ghostwheel (2)
- # hyperfiddle (13)
- # joker (2)
- # leiningen (82)
- # malli (3)
- # pathom (4)
- # polylith (12)
- # releases (3)
- # spacemacs (7)
- # sql (3)
Ska spendera dagen med att flytta över en reagent+re-frame app till dumdom och tar gärna emot tips och trix för det. Just nu undrar jag om någon har en clj-kondo config att dela med sig av 😄. Är kanske även dags för en #dumdom-kanal?
Artig 🙂 Jeg har dessverre ikke noe clj-kondo config å gi deg. dumdom burde nok shippe sin egen config så du slipper. Kanskje @U07FCNURX sitter på noe?
Om det inte finns en config så kan jag ta fram en. 😃 Just nu gick vi runt det för defcomponent genom att göra ett macro som tar argumenten i samma ordning som defn. (Det var så det var gjort i ett annat projekt här.)
Jeg har ikke laget noen dumdom clj-kondo macro-definisjon, dessverre. Du må gjerne dele hvis dere lager en!
Det här fungerar för att få en knapp att skicka iväg ett event baserat på ett fält i samma formulär:
(defcomponent foo []
(let [!input-element (atom nil)
do-something! (fn []
(dd/dispatch-event-data nil [[:foo/do-something (.-value @!input-element)]]))]
[:div
[:label {:for "do-something-input"}
"Do something with:"]
[:input#do-something-input {:ref (fn [e]
(reset! !input-element e))
:on-key-down (fn [e]
(when (= (.-keyCode e) 13)
(do-something!)))
:type :text}]
[:button {:on-click (fn [_e] (do-something!))}
"Do something!"]]))
Men jag undrar om det bara är mina reagent-reflexer som spökar. Finns det ett dumdommare sätt att göra det?Så du kan ihvertfall forenkle det til:
(defcomponent foo []
[:div
[:label {:for "do-something-input"}
"Do something with:"]
[:input#do-something-input
{:on-key-down (fn [e]
(when (= (.-keyCode e) 13)
(dd/dispatch-event-data nil [[:foo/do-something (-> e .-target .-value)]])))
:type :text}]
[:button {:on-click (fn [_e] (do-something!))}
"Do something!"]])
Desto større grunn til å bruke :on-input
Jeg ville brukt on-input
til å lagre verdien i feltet, og så hatt en form
rundt med on-submit
som "gjør noe" med den verdien
dumdom er laget for å ikke ha lokal tilstand, så "the dumdom way" er å lagre det i en global store
Omtrent sånn ville jeg gjort det:
(dd/set-event-handler!
(fn [e data]
(->> data
(walk/postwalk
(fn [x]
(cond
(= x :event/target.value)
(some-> e .-target .-value)
:else x)))
process-event-data)))
(defcomponent foo []
[:form {:on-submit [[:foo/process-value]]}
[:label {:for "do-something-input"}
"Do something with:"]
[:input#do-something-input
{:on-input [[:foo/collect-value :event/target.value]]
:type :text}]
[:button {:type "submit"}
"Do something!"]])
Förstår jag rätt om formuläret nu samlar in data i app-state och [[:foo/process-value]]
-hanteraren läser det därifrån?
Alternativt kan du sette dataene fra store rett inn i [[:foo/process-value]]
, men da ville jeg gjort det utenfor komponenten
(defcomponent foo [{:keys [submit-actions input-actions]}]
[:form {:on-submit submit-actions}
[:label {:for "do-something-input"}
"Do something with:"]
[:input#do-something-input
{:on-input input-actions
:type :text}]
[:button {:type "submit"}
"Do something!"]])
(defn prepare-foo [state]
{:input-actions [[:action/assoc-in [:foo] :event/target.value]]
:submit-actions [[:action/process-value (:foo state)]]})
(render (foo (prepare-foo @app-state)) el)
Tanken med dette er at foo
(komponenten din) kan være en helt generisk UI-komponent, og så mapper du domenet ditt til UI-data i helt vanlige funksjoner.
Jag hänger inte helt med. Men låt mig ge lite mer sammanhang. I reagent+re-frame skulle jag ha det så här:
(defn some-view []
[:<>
[bar]
[foo]])
Sedan tar bar
och foo
ut vad de behöver via subscriptions och dispatchar eventuellt events. Man kan läsa foo
i isolering och se vad den konsumerar och vad den gör. Just nu har hag med dumdom:
(defcomponent some-view [data]
[:div
[bar data]
[foo]])
Jag behöver uppenbarligen lära om, men jag är också väldigt förtjust i att mina komponenter är enbart vektorer… Det gör det lätt att manipulera dem med Clojure, t ex.dumdom legger opp til at du sender data gjennom komponent-treet og har all tilstandshåndtering utenfor ui-komponentene
Ja, jag tror att jag tycker om det. Det gör komponenterna lättare att testa. Jag försöker förstå hur mycket det påverkar i övrigt för hur jag kan göra saker.
hvis du er vant til å ha subscriptions inne i komponentene så blir det nok en ganske annerledes modell 🙂
I min fungerande foo
ovan har jag inte ens någon data in. Så det kan inte vara subscriptions-reflexen som spökar, tänker jag. 😃
En annan sak. När jag använder ett form
och on-submit
så laddas hela min SPA om. Använder du något trick för att undvika det?
Until I have grokked more of this my form component looks like so:
(defcomponent do-something-form []
(let [store-path [:store :do-something-input]]
[:form {:on-submit [[:store/assoc-in store-path]]}
[:label {:for "do-something"}
"Do something with:"]
[:input#do-something {:type :text
:on-input [[:store/assoc-in store-path]]}]
[:button {:type :submit}
"Do something!"]]))
Basically it is your suggestion, minus some of the generalisations of the task. WDYS?@U0ETXRFEW Jeg har også skrevet litt om dette her: https://www.kodemaker.no/blogg/2020-10-samspill-mellom-generiske-ui-komponenter/
@U0ETXRFEW du er på rett vei! Jeg vet ikke hva din :store/assoc-in
gjør, men jeg ville tenkt at den burde si noe om verdien også, ikke bare pathen. Ellers er målet med denne måten å kode på at du får generiske UI-komponenter som kun representerer designet ditt, og kan gjenbrukes til mange typer data. For å få til det bør detaljer sånn som store-path
sendes inn til komponenten. Anbefaler ellers bloggposten til @U07FCNURX 😊
Tack så mycket, båda två. Jag kommer ha stor nytta av det här samtalet. @U9MKYDN4Q, kan du utveckla frågan om store-eventet borde säga något om världen? Mitt problem här var initialt att jag inte hittade ett snyggt sätt att ge knappen kunskap om det. Så lösningen blev att låta eventhanteraren plocka ut det. Känns ovant, re-frame-hanterare sällan tillgång till dom-event. Dumdom verkar bjuda in till det. Jag har inget mål just nu att göra generiska komponenter, men det borde jag kanske ha.
@U0ETXRFEW Så du forslaget mitt over? Der har du bare en placeholder for verdien i action-dataene, og så plukkes den faktiske verdien ut av eventet i "maskineriet". Altså:
(def store (atom {}))
(defn process-event-data [actions]
(doseq [[action & args] actions]
(case action
:action/assoc-in (apply swap! store assoc-in args))))
(dd/set-event-handler!
(fn [e data]
(->> data
(walk/postwalk
(fn [x]
(cond
(= x :event/target.value)
(some-> e .-target .-value)
:else x)))
process-actions)))
Det var det exemplet som gjorde att jag vågade. 😀 jag förstår det bättre nu när jag tagit några stapplande steg i den riktningen.
Hei, dere! Er det noen her som er på jakt etter Clojure-jobb for tida? Jeg har en på tråden som leter etter folk, som fremstår som seriøs. > I am not contacting you regarding a position to recruit you for (as it looks to me like you just started a new position at a fun company), rather I was hoping to enlist any guidance you may have to offer regarding a Clojure Backend Developer given that you head up the clojure meetup group in Oslo. > > It has been exceptionally difficult to find someone that has experience working with a functional programming language, such as Clojure - that is a backend developer. I didn't know if you had anyone from previous experiences that might have a real passion for this technology & Ruby Rails, that may be looking for this type of role.
Lurer på hva vedkommende egentlig ser etter når de spesifiserer så nøye at det skal være en backend-utvikler
Jeg antar at de leter etter noen spesifikke kvalifikasjoner, og ikke prøver å unngå folk som kan noe om frontend? 😄
og at de må ha erfaring fra Clojure spesifikt, er vel mang en utvikler som er på utkikk etter sin første Clojure-erfaring vil jeg tro 🙂 (edit: hot take redacted, de sa et språk “such as Clojure”)
Jeg er litt rådvill på en sak, ønsker innspill.
1. Jeg har skrevet en Playwright-test i Clojure som jeg er fornøyd med.
2. Testen går gjennom 130 URL-er og sjekker at nettsiden oppfører seg som forventet.
3. Den tar i dag fire minutter å kjøre.
4. Jeg ønsker å parallellisere. Jeg tenker å prøve å sjekke 20 URL-er samtidig.
5. Flaskehalsen er å vente på nettverket og minnebruk for Playwright, ikke tallknusing på CPU.
Jeg tror ikke pmap
er det jeg vil ha, fordi jeg ønsker å ha kontroll på minnebruken.
Jeg tror det jeg ønsker er en arbeidskø foran, 20 workers som tar arbeid fra køen, og en resultatkø på andre siden med ferdigtygget rapport om hvordan det gikk.
Hvor ville du startet?
core.async! 20 channels som consumer fra en hoved-channel som er køen over jobber som skal utføres
https://clojuredocs.org/clojure.core.async/pipeline eller https://clojuredocs.org/clojure.core.async/pipeline-blocking
(det ble “fyr av melding og gå til lunsj her”, kanskje dårlig slack-skikk. Skal prøve og komme tilbake til dere når jeg har prøvd!)
Jeg stilte et lignende spørsmål for en liten stund siden. Kanskje det kan være noe nyttig for deg i den tråden også 🙂 https://clojurians.slack.com/archives/C053AK3F9/p1706446932485359
Hvis url-ene ikke må sjekkes i en spesiell rekkefølge, ville jeg i akkurat dette tilfellet holdt meg unna async-makroer, threadpools og køer, og begynt med noe slikt:
(def *result (atom {}))
(defn process-url [url result]
(let [response-time (rand-int 400)
status (rand-nth [200 404])
#_#_ {:keys [response-time status]} (hato/get url)]
(Thread/sleep (java.time.Duration/ofMillis response-time))
(swap! result assoc url {:response-time response-time
:status status})))
(let [urls (range 130)
concurrency 20
semaphore (java.util.concurrent.Semaphore. concurrency)]
(->> urls
(run! #(Thread/startVirtualThread
(fn []
(try
(.acquire semaphore)
(process-url % *result)
(finally
(.release semaphore))))))))
Her fyrer vi opp en virtuell tråd per url “Erlang-style”(?), lar 20 om gangen jobbe, og putter resultatet løpende inn i et atom. Forutsetter at du er på Java 21:)
(Hvis jeg ikke drømte, hørte jeg noe på en podcast nylig om at man vurderer å skrive om core.async til å bruke virtual threads under the hood istedenfor dagens makro-magi(?).)Interessant grep å bruke tilfeldighet, ja. Får inntrykk at det tar oss inn i property-testing-territorium?
Hva mener du med å unngå sjekke url to ganger? Den “køer” jo hver url fra urls én gang.
> (Hvis jeg ikke drømte, hørte jeg noe på en podcast nylig om at man vurderer å skrive om core.async til å bruke virtual threads under the hood istedenfor dagens makro-magi(?).) (edited) Hvis det viser seg at du ikke drømte og tilfeldigvis finner den podkasten, er jeg interessert i å høre den jeg og! 😄
Må sjekke:) Tror isf det var Alex Miller som var på Apropos og snakket om Clojure 1.12.
vet du om Java 21 virtual threads har samme mulighet til å “liste opp hva som kjører nå” som Erlang har? Såvidt jeg vet kan man kjøre en form for “ls” på kjørende tråder i erlang og faktisk drepe prosesser — så de ikke bare blir “borte i bakgrunnen”. (spør av ignoranse, jeg har aldri brukt Erlang)
Det fine her er at du kan bruke din vanlige synkrone http-klient inne i process-url:
(let [{:keys [response-time status]} (hato/get url)]
..)
Tror noe i den retningen kommer delvis via Structured Concurrency, som er i preview. Ser meget interessant ut. https://openjdk.org/jeps/453
Synes det er utrolig tøft at java-folka fremdeles jobber med ting som dette. Ikke “la la la-land, java er best”, men “dere, her har Erlang gjort noe lurt. Skal vi tilby noe på JVM-en som gir noe tilsvarende?”
(Apropos Apropos, så er det https://vimeo.com/884772901#t=1:12:0, og cirka 1:12:0 ut i den. Fra https://vimeo.com/884772901#t=1:24:50 cirka 1:24:0 snakker de om c.async og virtual threads. Hele episoden er interessant ift Java interop!)
Ulempen med approachen her er å vite når alle 130 er klare. Akkurat nå vet du ikke hvor lenge du må vente til (count @*result) er 130.
Structured concurrency kan være en løsning. Kanskje det her kunne vært en agent istedenfor et resultat-atom. Men jeg hadde begynt med noe slikt:
(def *result (atom {}))
(def done (promise))
(defn process-url [url result]
(let [response-time (rand-int 400)
status (rand-nth [200 404])
#_#_ {:keys [response-time status]} (hato/get url)]
(Thread/sleep (java.time.Duration/ofMillis response-time))
(swap! result assoc url {:response-time response-time
:status status})))
(let [urls (range 130)
concurrency 20
semaphore (java.util.concurrent.Semaphore. concurrency)
latch (java.util.concurrent.CountDownLatch. (count urls))]
(->> urls
(run! #(Thread/startVirtualThread
(fn []
(try
(.acquire semaphore)
(process-url % *result)
(finally
(.countDown latch)
(.release semaphore)))))))
(.await latch)
(deliver done :done))
• eventuelt returnere latch, og kjøre (.await latch) på utsiden • eller få inn en promise som man kjører deliver på • eller returnere en promise, som man kjører deliver på kanskje noe slikt?
(defn process-urls [concurrency result urls]
(let [semaphore (java.util.concurrent.Semaphore. concurrency)
latch (java.util.concurrent.CountDownLatch. (count urls))
p (promise)]
;; start én virtual thread per url
(->> urls
(run! #(Thread/startVirtualThread
(fn []
(try
(.acquire semaphore)
(process-url % result)
(finally
(.countDown latch)
(.release semaphore)))))))
;; start en virtual thread som venter på alle jobbene og sier "ferdig"
(Thread/startVirtualThread
(fn []
(.await latch)
(deliver p :done)))
;; returner promise, som blir levert når alt er klart
p))
Og så:
@(process-urls 20 *result (range 300))
(Structured concurrency i preview vil kunne erstatte dette med å holde styr på jobbene og sluttresultatet, men eksemplet her kan funke i mellomtiden.)Eventuelt:
(defn process-vthread [concurrency result f coll]
(let [semaphore (java.util.concurrent.Semaphore. concurrency)
latch (java.util.concurrent.CountDownLatch. (count coll))
p (promise)]
;; start én virtual thread per element
(->> coll
(run! #(Thread/startVirtualThread
(fn []
(try
(.acquire semaphore)
(f % result)
(finally
(.countDown latch)
(.release semaphore)))))))
;; start en virtual thread som venter på alle jobbene og sier "ferdig"
(Thread/startVirtualThread
(fn []
(.await latch)
(deliver p :done)))
;; returner promise, som blir levert når alt er klart
p))
@(process-vthread 20 *result process-url (range 300))
Når jeg har skrevet Go-kode tidligere for liknende problem, har jeg typisk tatt inn en kanal som arguemnt som funksjonen har levert “nå er det ferdig!” på. Bruken blir vel ganske lik her.
Ja. Nåværende funksjon kan kalles som fire-and-forget uten promise, hvis man ikke har behov for å vente på alt og derefe promisen.
Ellers bare å ta inn p
utenfra istedenfor, så har du Go-mønsteret:)
@U0523NZUF Jeg ville bare si tusen takk en gang til, koden du kom med var til stor hjelp. Jeg har endelig fått satt av nok tid til å skjønne hva som skjer, og endre litt. Vedlagt har jeg prøvd å se på hva som skjer underveis. Blå betyr “venter på å starte”, oransje betyr “under arbeid” og grønn betyr “ferdig”. 🙏
Her er et eksempel med pipeline-blocking
fra core.async også, tungt basert på et clojuredocs-eksempel.
Dette var mye mindre kode enn jeg hadde forventet.
fetch next X rows only
er visstnok det som har blitt standard, LIMIT var noe postgres/mysql la til før standard var etablert 😮
Med SQL er det litt som med emacs: ingen sin sql/emcas er lik som andres 😹
Uten helt å vite hva jeg driver med, forsøker jeg å koble sammen http-kit, reitit.ring og ring.middleware.reload, men det fungerer ikke. 😞 Mitt forsøk så langt:
(ns quiz.main
(:require
[hiccup2.core :as h]
[org.httpkit.server :as hk-server]
[reitit.ring :as ring]
[reitit.ring.middleware.dev]
[ring.middleware.reload :as reload]
))
(defn template
[content]
(str (h/html [:html
[:head
[:script
{:src "",
:integrity
"sha384-EAzY246d6BpbWR7sQ8+WEm40J8c3dHFsqC58IgPlh4kMbRRI6P6WA+LA/qGAyAu8",
:crossorigin "anonymous"}]]
[:body
[:header
[:h1 "Martin Quiz!"]]
[:main content]]])))
(defn home [_req]
{:status 200
:headers {"Content-Type" "text/html"}
:body (template
[:section
"test"])})
(def app
(ring/ring-handler
(ring/router
[["/" {:get {:handler home}}]])))
(defn -main []
(let [handler (reload/wrap-reload #'app)]
(println "Starting server")
(hk-server/run-server handler {:port 8080})))
Jeg har tatt inspirasjon herfra: https://http-kit.github.io/migration.html#reload
Hm ... det fungerer kanskje som det skal: Når jeg lagrer filen og refresher siden, så er endringen fanget opp. Jeg var ute etter at det refreshes automatisk også slik at endringene vises med en gang på siden, som jeg er blitt vant med i forbindelse med React-utvikling.
Jeg fant nettopp denne, og jeg skal prøve noe lignende: https://andydote.co.uk/2023/11/15/hot-reload-for-serverside-rendering/
https://github.com/weavejester/ring-refresh fungerer, 🎉 (Men man mister vel state.)
Sånn jeg har gjort det: 1. Putter Http-kit-serveren i et atom, så jeg kan skru den av og på 2. Med Reitit regenererer jeg Router-tabellen på hver request. Da mister jeg ikke state.
Fikk den siste rett fra Reitit-dokumentasjonen: https://cljdoc.org/d/metosin/reitit/0.7.0-alpha7/doc/advanced/dev-workflow
MEN det er jo ikke automatikk i at det skjer når jeg lagrer. Jeg jobber fremdeles i en REPL. Så det løser kanskje ikke det du ba om.
Nice, takk innspill! Jeg er ganske fornøyd med hvordan det ble til slutt, og jeg tror jeg har løst problemet vedrørende state ved at jeg holder state i et separat navnerom. Når jeg lagrer nå, så reevalueres det gjeldende navnerommet, og nettsiden oppdateres.
Jeg lurer litt på hva som skjer om jeg splitter koden opp over flere filer, men, for jeg har fremdeles alt i én fil, utenom state-biten.
Skjønner, ser den. Jeg har lett litt etter løsning på “cirka samme problem”. For meg ble det til at jeg begynte å grave i å bruke #portfolio (som @U9MKYDN4Q har laget). Det går visst ann å bruke Portfolio med en server-side Clojure-app, men å ha et tynt (separat) clojurescript-bygg som kun viser “dumme komponenter”, der komponentene er skrevet i cljc, tar argumenter, og returnerer hiccup. Men jeg satte meg fast i clojurescript-bygg. Jeg gikk for shadow-cljs (siden jeg har brukt shadow før). Så ble det neste dag, så fortsatte jeg med “re-evaluér http handlers i REPL, alt+tab, F5”. (mer info i https://clojurians.slack.com/archives/C053R9G2C4V/p1697279327626169)
En ting jeg ikke liker med sql er alle navnet på alle join variantene leftrightinnerfullouterjoin lissom?
må fortsatt halvjevnlig innom her https://blog.codinghorror.com/a-visual-explanation-of-sql-joins/