Fork me on GitHub
#clojure-norway
<
2021-08-04
>
james11:08:33

@isak Den lignar veldig på loop-makroen frå Common Lisp. Kanskje nokon alt har implementert det i Clojure?

james11:08:42

Eh, never mind, det blir vel ikkje heilt det same.

isak14:08:12

Sjekket den og jeg synes du hadde rett i det. Et eksempel jeg fant:

(let ((s "alpha45"))
  (loop for i from 0 below (length s)
	for ch =  (char s i)
	when (find ch "0123456789" :test #'eql)
	return ch) )

isak14:08:47

Jeg tenkte faktisk på å implementere :return-some, og jeg ser de har noe lignende:

;; Several actions provide shorthands for combinations of when/return

(loop for x in '(foo 2) 
      thereis (numberp x))
T

james10:08:50

Ja, men (AFAIK) kan du ikkje ha fleire for-blokker etter kvarandre slik som i din makro.

3
Ivar Refsdal11:08:05

Einig i at nøsting av ifs ikkje alltid vert vakkert. Ei (anna) mogleg løysing er å bruke failjure: https://github.com/adambard/failjure Ser ganske bra ut. Samtidig er eg ikkje altfor glad i å bruke makroer frå bibliotek i vanleg kode

isak15:08:28

Har ikke brukt failjure, men interesant. Men fordi den bruker monads tror jeg den kan ha store konsekvenser for performance. Her er try, for eksempel:

(defn try-fn [body-fn]
  (try
     (body-fn)
     (catch #?(:clj Exception :cljs :default) e# e#)))


; Exceptions are failures too, make them easier
(defmacro try* [& body]
  `(try-fn (fn [] ~@body)))
Så alle slike ting blir wrapped i en ny funksjon.

Ivar Refsdal19:08:20

Hm. Sikker på det vil ha store konsekvensar? Kan ikkje JVM-en uansett inline funksjonen?

isak20:08:23

Sjekket nå:

(time
    (dotimes [i 1000000]
      (f/try-all
        [a (f/try* 1)
         b (f/try* 1)]
        (+ a b))))
"Elapsed time: 37.7033 msecs"
(time
    (dotimes [i 1000000]
      (+ 1 1)))
"Elapsed time: 3.083 msecs"

isak20:08:03

(time
    (dotimes [i 1000000]
      (step
        :when-let [a 1
                   b 1]
        (+ a b))))

"Elapsed time: 6.441 msecs"

isak20:08:26

Samme som:

(time
    (dotimes [i 1000000]
      (when-let [a 1]
        (when-let [b 2]
          (+ a b)))))
"Elapsed time: 6.1713 msecs"

Ivar Refsdal07:08:28

Her måler du to forskjellige ting? Det riktige å måle er:

(time
    (dotimes [i 1000000]
      (when-let [a (try 1 (catch Exception e e))]
        (when-let [b (try 2 (catch Exception e e))]
          (+ a b)))))
? Men einig i at det går litt raskare utan funksjonskall.

Ivar Refsdal08:08:37

På mi (gamle) maskin gjev funksjonskall ca. 5-6 ns overhead etter det eg forstår:

(comment
  (time
    (let [start-time1 (System/nanoTime)
          cnt 1e9
          res (dotimes [i (int cnt)]
                (when-let [a (try 1 (catch Exception e e))]
                  (when-let [b (try 2 (catch Exception e e))]
                    (+ a b))))
          spent-time1 (- (System/nanoTime) start-time1)

          start-time2 (System/nanoTime)
          res2 (dotimes [i (int cnt)]
                 (f/try-all
                   [a (f/try* 1)
                    b (f/try* 2)]
                   (+ a b)))
          spent-time2 (- (System/nanoTime) start-time2)
          overhead (- spent-time2 spent-time1)]
      (format "overhead: %.2fns per call" (double (/ overhead (* 2 cnt)))))))
For ein webserver kan ein då gjere 200K funksjonskall og få 1ms ekstra latency i responstid (kontra inlining). Mogleg eg tar heilt feil med desse tala..

isak14:08:00

Det er nok riktig med det utgangpunktet, men det som intereserer meg mere er hva som er idiomatisk med de forskjellige teknikkene, og da perf. Jeg tenkte ikke spesielt bare på funksjonskall. Men det blir litt subjektivt hva som er idiomatisk med failjure osv, så jeg tror ikke jeg kan overbevise deg hvis du er i tvil. Det vil alltid være en forskjell man peke til som kanskje er unfair.

Ivar Refsdal08:08:02

Ja OK, eg forstår poenget ditt betre no. Eg er forøvrig einig i at din makro alltid vil ha høgare ytelse. Den ser også ganske leseleg ut, synest eg. Usikker på om det er ein god ide eller ikkje. Utfrå mi googling og eige erfaring, så er det opplagt at mange har bruk for early return/short circuit. Er det nokon god grunn til at ein ikkje har det i Clojure? Eg har ikkje funne noko godt svar på det. Sjølv har eg brukt exceptions (i yada og pedestal, med interceptor lengre ned i chainen som catcher den spesifikke typen av exceptions) for å løyse dette.

3
isak21:08:38

Enig, jeg har egentlig samme spørsmål. Selv kan jeg ikke se en god grunn. Jeg spurte om det i F#, da de har samme begrensning, men fikk ikke noe godt svar. Det eneste folk nevnte var at de synes funskjoner er lettere å forstå hvis det er bare 1 return site, men det er ganske subjektivt, og spørs på hva slags funskjon synes jeg. Return early for error-handling eller lignende virker ikke spesielt tricky.