Fork me on GitHub
#beginners
<
2019-06-21
>
Crispin02:06:25

@markx you could implement it exactly like you explained it:

(ns delay-print)

(defonce print-fut (atom nil))

(defn do-after [delay func & args]
  (when-let [fut @print-fut]
    (when-not (future-done? fut)
      (future-cancel fut)))
  (reset! print-fut (future
                         (Thread/sleep delay)
                         (apply func args))))

#_ (do-after 5000 println "This is a test")

Crispin02:06:07

can only be one delay at a time though. has "global" state

Nate Sutton02:06:21

you could probably put together a sort of debouncing library for this sort of thing, it'd be interesting

Nate Sutton02:06:40

I'd probably still use the timeout channel with global state but only because I bet there's trickery behind how the timeout works to make it more efficient than blocking a whole thread with a sleep

Nate Sutton02:06:43

I'd just stick the quit channel in the global state and clear it after the timeout

Nate Sutton02:06:40

there's definitely something going on but I won't pretend to completely understand it https://github.com/clojure/core.async/blob/master/src/main/clojure/clojure/core/async/impl/timers.clj#L58

andy.fingerhut02:06:48

I do not have any suggestions for improvement on the do-after above, but it does look like there might be some very minor races/edge cases in that implementation. For example, future-done? could return false while the future is in the process of executing, and is part-way through executing the provided func. I don't know what are all of the possibilities of the effects future-cancel in that situation.

Crispin02:06:09

future-cancel called on a finished future just returns false. I can only see a possible condition between cancelling the future and reset!ing in a new one, but that depends on what other thread may also be dereferencing that atom. Another thread may deref and receive a cancelled future.

andy.fingerhut02:06:45

Suppose the user's provided function takes 1 sec to execute, and someone calls do-after during that 1 second?

Crispin03:06:30

yep. gotcha.

Nate Sutton02:06:07

that's a good point, I guess the channel version assumes that once the post-timeout operation begins it is not to be interrupted

Nate Sutton02:06:47

and what futures do when they cancel is a total mystery to me

Nate Sutton02:06:11

could they leave an operation half done by suspending execution entirely?

andy.fingerhut02:06:41

I do not know, but I would feel a bit leery about relying on the implementation above until finding out for sure.

Nate Sutton02:06:50

with a quit channel you could pass the channel to the operation so that it could check that it is open periodically (if it's a long operation) so that it could gracefully shut down

Nate Sutton02:06:12

god there's so much contingent on what you're doing with it

Nate Sutton02:06:26

it turns out concurrency is hard, who knew

andy.fingerhut02:06:36

If the desired behavior was "it is ok when do-after is called to 'reschedule' if the user's function has not yet begun, but once it has begun, it should be allowed to complete", then you could store in the atom the status of whether it is waiting, or the user's function is currently executing, and update that inside the future. Some compare-and-set! operations on that atom are probably needed within the future code, as well as in do-after.

Crispin02:06:25

the future already has that state field in :pending. You may be overcomplicating things. I would think the clojure.core future api is atomic. And in all my use of it I've never had any future/start/cancel/deref race conditions.

Nate Sutton02:06:49

what does it do when it's cancelled?

Nate Sutton02:06:05

does it suspend operation immediately or something?

Crispin02:06:43

inside the thread you get an exception raised. You will get that exception raised in the mainline when you deref the cancelled future

andy.fingerhut03:06:01

If future-cancel "kills" the future thread during the Thread/sleep call, and the future thus never starts executing func, that sounds like the nicest thing that could happen. But future-cancel could be called while func is in the middle of execution, too.

Crispin03:06:50

yep. future-cancel could rip the floor out from under your function

Crispin03:06:46

i think the big tradeoff with this code is that this is using a thread and gets the heaviness of that. So for instance if you expanded it to not have a single global state (you could run many different 'cancel' timelines), for each delay you launch you spawn a thread. That's not necessairly going to scale to many thousands of them at once.

Crispin03:06:30

its just offloading the probelm to the OS 🙂

Nate Sutton03:06:24

the semantics are so undefined ¯\(ツ)

Crispin03:06:58

I have a similar situation in production code that handles a large load of many things 'expiring', and I implemented it with expiry timestamps, not delays. And a single thread 'cleaning' all of them.

Nate Sutton03:06:04

I think clojure uses a single thread for channel timeouts globally, and a go block that blocks on channels gets parked, so that might be the more efficient route

Matthew Curry03:06:24

Hey guys, trying to do stuff with clojure + tools.deps + neanderthal for the first time together. The neanderthal docs say, if java is >=9, to pass --add-opens=java.base/jdk.internal.ref=ALL-UNNAMED to the JVM. How does specify this in deps.edn?

Matthew Curry03:06:52

what I tried:

{:deps 
 {
  uncomplicate/neanderthal {:mvn/version "0.25.0"}
  criterium {:mvn/version "0.4.5"}
  }
 :jvm-opts ["--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED"]}

Matthew Curry03:06:42

error I’m getting:

Matthew Curry03:06:51

Execution error (IllegalAccessError) at uncomplicate.commons.core/eval238$fn (core.clj:69).
class uncomplicate.commons.core$eval238$fn__239 (in unnamed module @0xe664af4) cannot access class jdk.internal.ref.Cleaner (in module java.base) because module java.base does not export jdk.internal.ref to unnamed module @0xe664af4

alexmiller10:06:23

You can only put jvm-opts in aliases right now

alexmiller10:06:48

So you’ll need to do that, then activate the alias at the command line

👍 1
markx05:06:55

@nate_clojurians I have this impression that you are a big fan of core.async. I actually don’t use it that much. I personally really like CSP, and I think using it in Golang is so comfortable. But on the other hand, in Golang goroutines and channels are you only option, if you don’t want to use locks. As for Clojure, since there are already atom, ref and other things that are thread-safe, in many situations you don’t really need to use core.async to get the job done. And considering it’s a lib and not baked into the language, some times I feel using it complicates my code and thus is unnecessary.

Nate Sutton05:06:13

I'm just a big fan of some of the properties of go blocks and timeouts using channels. go blocks get parked while waiting on channels so they're not blocking threads and that's really valuable. if it was just concurrent data storage/access I'd use something else

James Sully07:06:31

(map rand-nth (replicate 5 (range 5))) How do I do this without copying the range a bunch, using library functions (as opposed to explicit looping)? The awkward thing is that as rand-nth isn't pure, I can't just rewrite as (replicate 5 (rand-nth (range 5))), as I want a different choice each time.

James Sully07:06:30

repeatedly seems close to, but not quite, what I want

James Sully07:06:17

Turns out repeatedly was what I wanted - (repeatedly 5 #(rand-nth (range 5))) I didn't realise you could create nullary functions this way

Crispin07:06:50

theres also (rand-int 5)

Iwo Herka09:06:44

Quick question: A function to split coll into N chunks? partition splits the coll into M chunks of N elements.

Andrew10:06:21

(fn [n coll] (apply map list (partition n coll))) should do it

Andrew10:06:21

And will drop elements that aren't partitioned

Andrew10:06:45

Perhaps a starting point for something more correct 😉

Iwo Herka10:06:46

Cool thanks 🙂

jumar11:06:42

That doesn't quite work - it doesn't pick the successive elements but rather every nth element:

(apply map list (partition 3 (range 14)))
;;=> ((0 3 6 9) (1 4 7 10) (2 5 8 11))
I'd do it by "counting" although that's not lazy:
(defn partition-n [n coll] 
                            (let [m (int (/ (count coll) n))]
                              (partition m coll)))
(partition-n 3 (range 14))
;;=> ((0 1 2 3) (4 5 6 7) (8 9 10 11))
not sure if it can be completely lazy but would be interesting to see a better idiom for this

Andrew12:06:43

The requirements didn't say anything about the orders of the elements 😉

magra12:06:55

Hi, I see code with a defn wrapped in a let. Like so:

(let [my-fun1 (fn [a] (dosomething))]
    (defn my-defn [b]
        (let [my-fun2 (fn [b] (dosomething else))]
            (body))))
What is the purpose or difference between the outer and the inner let. Is this for some closure purpose?

manutter5112:06:40

I would not do defn inside a let, defn is meant to be used at the top level of a namespace. I would do something like this:

(def my-defn
  (let [my-fun1 (fn [a] (dosomething))]
    (fn [b]
      (let [my-fun2 (fn [b] (dosomething else))]
        (body)))))

manutter5112:06:30

The outer let defines some local vars and then returns a function. The inner let runs whenever you run the function that the outer let returns.

manutter5112:06:25

That example is a bit obscure/abstract though, it’s just illustrating a general concept.

manutter5112:06:53

Here’s a more explicit example:

(def outer-let
  (let [level-0 (fn [a] (println (str "Calling (level-0 " a ")")))
        _ (println "Executing the outer let")]
    (fn [b]
      (let [level-1 (fn [c] (println (str "Calling (level-1 " c ")")))
            _ (println "Executing the inner let")]
        (println "Executing the main body of the fn")
        (level-0 "foo")
        (level-1 "bar")
        (println "The value of b is" b)
        (println "Done.")))))
Executing the outer let
=> #'user/outer-let
(outer-let :baz)
Executing the inner let
Executing the main body of the fn
Calling (level-0 foo)
Calling (level-1 bar)
The value of b is :baz
Done.
=> nil

magra12:06:09

@manutter51 Thanx! That is how I would have expected it to be. I was trying to figure out https://github.com/fulcrologic/fulcro/blob/develop/src/main/fulcro_css/css_injection.cljc Line 49.

magra12:06:46

I know what it does, but do not see the advantage of the let being outside the defn.

magra12:06:42

Or is the outer let run at compile time unter clj and the inner at runtime unter cljs?

manutter5112:06:42

The outer let is run at compile time because it’s at the top level. The effect is that the things defined inside the let are visible to the inner defn(s), but only to the inner defn(s). So for example, on line 49 of the css-injection ns, the let defines a get-rules fn that compute-css can use. If you (require '[fulcro-css.css-injection :as ci]), though, and try to call (ci/get-rules components), you’ll see that get-rules was not actually defined inside the css-injection namespace. It’s a local fn inside the let, and does not exist outside the let. So it’s like what OOP programmers do when they define private methods, except without the OOP.

magra12:06:50

So it improves performace in contrast to defining get-rules in the inner let?

magra12:06:37

Would it not also have been a private method in the inner let?

manutter5112:06:35

I don’t think it improves performance at all. And yes, it would still have been private in the inner let. I really don’t see a good reason for the outer let in that case, but sometimes you get code like that as you go back and rewrite things. Maybe there used to be more than one defn inside the let, which would kinda make sense, since you’d be sharing a “private” function between two defn’s.

alexmiller12:06:23

unless you fully understand all the ramifications of using a top-level let, just don't do that

magra12:06:21

Ahh!! Thats it! Thanks!! In "neighboring" namespaces there are multiple defns in the let.

manutter5113:06:37

Outer lets are a way of defining functions that can never be tested. 😉

magra13:06:25

Good point!

magra13:06:54

I already have enough trouble testing them in inner lets.

Matt P13:06:18

Hey all I'm running into a problem where using CIDER and nREPL within emacs. I can run a function with a fully qualified namespace e.g., user> (clojure-noob.core/train) but not user> (train). I'm sure this has been asked before, but am I missing a step with lein/nREPL/cider?

Nate Sutton13:06:08

I think you need to require it into the user namespace

Nate Sutton13:06:32

or change to that namespace

magra13:06:07

In the cider repl you can press ',' (komma), which offers browse-ns.

Matt P13:06:32

Thanks Nate & magara. I imported the ns with user> (use 'clojure-noob.core) whenever I wanted to pull the namespace into my own

manutter5113:06:05

I usually use (require '[some.other.ns :as o]) so I can do (o/foo) and (o/bar) and so on. The use function is probably ok in the REPL (it’s not recommended in code), but even in the REPL you may have issues if you try to use more than one namespace at the same time.

vlad_poh15:06:00

any reitit users here?

vlad_poh15:06:18

struggling to add a custom http header

vlad_poh15:06:12

where should a custom header go in this example. I've looked through the documentation and examples but no specific example

["/upload"
         {:post {:summary "upload a file"
                 :parameters {:multipart {:file multipart/temp-file-part}}
                 :responses {200 {:body {:name string?, :size int?}}}
                 :handler (fn [{{{:keys [file]} :multipart} :parameters}]
                            {:status 200
                             :body {:name (:filename file)
                                    :size (:size file)}})}}]

manutter5115:06:11

Yeah, I just posted an answer in the #reitit channel. I do think it’s a lot easier to take the req itself as the handler fn argument, and then use let to pull out the parts you need. It’ll be a lot easier to read when you go back later to maintain the code. But if you want the destructured version check the other channel.

Stefan15:06:00

I know that -> is called the “threading macro”. Is there a similar name to refer to the .. macro?

alexmiller15:06:41

I don't think there is. I'd call it "nested member access" or something like that

Stefan15:06:09

Ok thanks. I want to describe it in my commit message, that’s why 🙂

Stefan15:06:46

So would this be considered a proper way of writing Clojure calling out to Java?

(ns metrics-server.gitlab-fetcher
  (:require [clj-http.client :as client]
            [clojure.string :as str]
            [clojure.data.json :as json])
  (:import java.time.ZoneId
           java.time.format.DateTimeFormatter
           java.time.temporal.TemporalAdjusters
           java.time.LocalDate
           java.time.DayOfWeek
           java.time.temporal.Temporal))

(def ^ZoneId zone-id (ZoneId/systemDefault))
(def formatter (DateTimeFormatter/ISO_OFFSET_DATE_TIME))

(defn date-to-string [^LocalDate date-time]
  (.. date-time
      (atStartOfDay)
      (atZone zone-id)
      (format formatter)))

(def ^Temporal last-monday
  (.. LocalDate
      (now)
      (with (TemporalAdjusters/previousOrSame DayOfWeek/MONDAY)))))
Or would an experienced Clojurist raise his/her eyebrows reading this?

alexmiller15:06:54

looks fine to me

Stefan15:06:22

Great, thanks for your feedback Alex!

alexmiller15:06:22

I'd be a little careful with last-monday - it bakes state into a top-level var (now is dependent on when this code is loaded/compiled)

Stefan15:06:31

Ah right. So better change it to a fn then I guess?

alexmiller15:06:57

either a function or you could wrap it in a delay and deref it when needed. it really depends on what your goals are

Stefan15:06:34

Sure. Didn’t run across delay yet, I’ll look it up. My use case is that I want to collect metrics from “last week” by passing “last Monday” and “before last Monday” as bounds. And then create reports basically.

(def before-last-monday
  (.. last-monday
      (with (TemporalAdjusters/previous DayOfWeek/MONDAY))))

alexmiller15:06:24

"last Monday" is relative to when you're running the program. if you're running it for a week, then that's bad. if this is a batch job, then it's probably fine

alexmiller15:06:32

similarly, ZoneId is relative to where you're running the program and may not play well with AOT

Stefan15:06:37

Right, that part is clear. So delay creates sort of a lazy singleton if I may call it like that, right? I’ll go for a fn I guess.

alexmiller15:06:51

I think a defn is probably good for last-monday

👍 1
alexmiller15:06:30

you might also want to throw (set! *warn-on-reflection* true) at the top of that namespace to catch reflection warnings

Stefan15:06:52

I agree with the ZoneId part. It’s a limitation I am willing to accept for now as this is a spike and I don’t expect it to be run in different time zones 🙂

alexmiller15:06:03

I think I would just inline the zone-id call, it's not worth the def

Stefan15:06:05

Yeah I was using lein check as well.

Stefan15:06:16

“worth” in terms of readability, or are there other costs I should be aware of?

alexmiller15:06:02

well it's actually probably faster to inline it (as you avoid a var lookup), and it eliminates a class of potential problems around loading and compiling

alexmiller15:06:15

those are probably not problems you actually have right now

alexmiller15:06:41

but you know how frequently prototype code becomes your production maintenance headache 5 yrs later

Stefan15:06:49

No I agree and also from a learning point of view that is great feedback

alexmiller15:06:56

I treat all top-level defs as suspicious :)

alexmiller15:06:41

because they have effects on the runtime when they are loaded or compiled, and that is a common source of subtle problems

Stefan15:06:29

Yeah I didn’t realise that before, but I can definitely relate to the class of problems with “subtle things during load time”.

Stefan15:06:08

Thanks for your time Alex, much appreciated!

Nate Sutton16:06:33

what kinds of problems can top level defs cause? I have a lookup table that holds a hashmap defined at the top-level I have a hard time imagining that being a problem

Nate Sutton16:06:39

ah shoot just saw a bug in ->coords

Nate Sutton16:06:04

oh nope, never mind

Nate Sutton16:06:26

range is inclusive at the low end and exclusive at the high end

manutter5116:06:27

Yeah, if your lookup table is never going to change, it’s fine to have it as a top-level def

Nate Sutton16:06:33

that's what I figured, cool

bronsa16:06:35

big constant top level defs can cause compilation to fail

bronsa16:06:59

small maps like that are perfectly fine

Nate Sutton16:06:31

what's a big constant top level def?

Nate Sutton16:06:47

trying to grasp the failure modes here

bronsa16:06:57

a top level def like that, but big :)

bronsa16:06:13

i.e. with lots of keys, or huge strings etc

Nate Sutton16:06:13

and why does it cause compilation to fail?

bronsa16:06:29

because the JVM only has a limited size for constants per class

bronsa16:06:52

can happen if you for example generate those defs from a macro or inline them from a config file

bronsa16:06:01

but yeah stuff like that in code is perfectly reasonable

alexmiller16:06:34

Like 1000 entries

alexmiller16:06:58

Usually constructed by a macro or something

Nate Sutton16:06:50

So what if you need a constant look up table like that?

Nate Sutton16:06:46

I guess generate it at runtime but I guess I don't know where you'd hang the data if you don't need concurrency control and you can't use a def

bronsa16:06:56

(def x (read-from-file)) is ok

bronsa16:06:23

it's just (def x the-literal-data) that could be problematic

alexmiller16:06:45

or put it in a delay

alexmiller16:06:20

so you defer until used (which could be when you use, or could be an explicit call made during startup)

Nate Sutton16:06:05

huh, ok, makes sense

Nate Sutton16:06:42

saw the delay bit used for spinning up the timeout daemon in core.async but didn't grok it

Nate Sutton16:06:13

thanks for the info, these sorts of techniques are hard to understand from just docs

alexmiller16:06:09

yeah, you have to experience a lot of personal pain from doing the wrong thing before you learn it :)

Nate Sutton17:06:56

I'd rather harvest the lessons of yours 😜

alexmiller17:06:02

if only my kids thought the same

😂 2
john18:06:32

It's so true though, I've found learning for me is most memorable when it's the painful, brute force "bang your head against all possibilities"

john18:06:55

Sometimes rewriting things 10 times

john18:06:16

And a master chess player once told me that that you only get better by losing against better opponents

chepprey18:06:05

"The master has failed more times than the student has even tried."

andy.fingerhut18:06:14

Someone wiser or more quotable than myself probably has a better version of the following: "A fool does not learn from their own mistakes. A smart person learns from their own mistakes. A person who learns from other people's mistakes is a genius."

andy.fingerhut18:06:27

That is more quotable, but he is a bit more generous in leaving out the category of people who do not learn from their own mistakes 🙂

seancorfield18:06:01

This is part of why it's really helpful to senior developers to mentor junior developers 🙂

☝️ 6
Nate Sutton18:06:46

also why guides are really helpful

jason poage19:06:39

What is the best way to store a hash map inside of a cookie?

jason poage19:06:00

the only ways i know of are serializing the data or storing as json

hiredman19:06:12

(those are the same thing, json is serialization)

Leland-kwong20:06:57

what does it mean to reify something in programming?

Leland-kwong20:06:08

I see a dictionary definition of make (something abstract) more concrete or real.

andy.fingerhut20:06:03

I do not know if everyone uses it consistently, but sometimes it is used to mean to take some kind of thing/object in the world of a programming language, and give it an explicit name, or create an object from it that you can have an explicit reference to, and pass it around like other objects.

noisesmith20:06:53

@leland.kwong I think in the clojure context the definition is very direct

noisesmith20:06:14

clojure.core/reify takes an interface, and generates a specific object of an anonymous class implementing the interface

noisesmith20:06:24

so it makes real / concrete some existing interface

✔️ 1
Leland-kwong20:06:40

that makes more sense now

Leland-kwong20:06:16

seems like the jist of it is something gets reified during compilation?

noisesmith20:06:52

right - an Interface is abstract, it's not a thing you can directly use

noisesmith21:06:00

reify creates a specific thing that implements it

Leland-kwong21:06:07

thanks a bunch

noisesmith21:06:37

with-open needs a Closable arg, and promises to close it

noisesmith21:06:41

user=> (with-open [_ (reify java.io.Closeable (close [_] (println "was closed")))] )
was closed
nil

noisesmith21:06:58

via reify, we can make a thing you can close

Alex Arslan21:06:12

Is clojure.math.numeric-tower still maintained? It seems not to have seen much development in recent years, and hasn't had a release in 5.

Alex Arslan21:06:46

I also can't figure out how to install it, but that's my fault for being an extreme Clojure n00b.

alexmiller21:06:05

You need to add it to your dependencies, how depends on what tooling you’re using

alexmiller21:06:36

The Readme has some examples

alexmiller21:06:01

Or maybe you’ve found the very old contrib version, not sure

Alex Arslan21:06:07

I had been attempting to specify it in deps.edn but couldn't figure out how to write things out correctly. The examples were helpful; I was very close, I just needed to prepend org. to what I already had.

Alex Arslan21:06:43

Hm, but I'm unable to do (use clojure.math.numeric-tower)

Alex Arslan21:06:01

Is that even the right syntax?

seancorfield21:06:12

(! 566)-> clj -Sdeps '{:deps {org.clojure/math.numeric-tower {:mvn/version "RELEASE"}}}'
Downloading: org/clojure/math.numeric-tower/maven-metadata.xml from 
Downloading: org/clojure/math.numeric-tower/maven-metadata.xml from 
Downloading: org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.pom from 
Downloading: org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.jar from 
Clojure 1.10.1
user=> (use 'clojure.math.numeric-tower)
nil
user=> 

❤️ 1
thanks3 1
Alex Arslan21:06:57

Perfect, thank you both!

seancorfield21:06:21

Like many Clojure libraries, it hasn't been updated in a long time because it's essentially "done" -- no additional work is needed.

👍 1
alexmiller21:06:39

$ clj -Sdeps '{:deps {org.clojure/math.numeric-tower {:mvn/version "0.0.4"}}}'
Downloading: org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.pom from 
Downloading: org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.jar from 
Clojure 1.10.1
user=> (ns example.core (:require [clojure.math.numeric-tower :as math]))
nil
example.core=> (math/expt 2 5)
32

seancorfield21:06:52

You'll find a lot of Clojure libraries that seem "abandoned" but in reality they're just really, really stable.

👍 1
alexmiller21:06:56

nothing in Clojure math has changed in many years, so the lib continues to do what it does

Alex Arslan21:06:16

That's great!

Alex Arslan21:06:02

I know very little about Clojure, so I don't know whether a particular version would introduce a change for which a library would need to compensate. I just typically get suspicious of repos that haven't been touched in quite some time.

seancorfield21:06:17

There's only one open issue against that library (from September) and it's a suggestion for a very, very minor performance improvement.

seancorfield21:06:15

You'll get used to this with Clojure: libraries are often small and very focused so quite a few of them are "complete", and Clojure itself is incredibly stable and almost never breaks backward compatibility. That's a core part of the Clojure ethos.

💯 1
alexmiller21:06:29

yeah, it's difficult to tell the difference. many Clojure libs have started adding "liveness" notices to indicate that the non-activity just means they're stable

👍 1
noisesmith21:06:50

another gotcha - you don't actually need math.numeric-tower, java.lang.Math has most of what you usually need

alexmiller21:06:06

yeah, I've done Clojure for 10 years and have never used it

alexmiller21:06:33

it really comes from Lisp tradition

noisesmith21:06:35

I often see newcomers who want numeric-tower because they are afraid to do interop

Alex Arslan21:06:38

I found math.numeric-tower via a Stack Overflow question that said that Java's math library's abs doesn't handle the full gamut of Clojure's numeric types

alexmiller21:06:02

there are some gotchas in the interop around the numeric types to be sure

seancorfield21:06:15

We've been using Clojure in production since 2011 and we use Alpha and Beta builds in production because it's so stable. We update to every new version as soon as alpha/beta builds appear and usually the "worst" we've run into is a newly-introduced core function might clash with a function we've written ourselves (and even that is usually not a breaking change, you just get a warning).

👍 1
alexmiller21:06:15

without some Java experience, these can be challenging to navigate

Alex Arslan21:06:39

I have 0 Java experience 😛

alexmiller21:06:53

yeah, ask here! people will help :) or in #java

❤️ 1
alexmiller21:06:09

@U04V70XH6 that scenario should never be a breaking change

noisesmith21:06:24

(ins)user=> (Math/abs (/ -3 2))
Execution error (IllegalArgumentException) at user/eval144 (REPL:1).
No matching method abs found taking 1 args
(ins)user=> (Math/abs (/ -3.0 2))
1.5

alexmiller21:06:04

in the first case, you're getting Clojure's ratio type

✔️ 1
alexmiller21:06:09

which Java doesn't understand

seancorfield21:06:19

@U064X3EF3 Indeed, but I didn't want to make the "never" blanket assurance 🙂 You are entitled to give that 🙂

😄 1
alexmiller21:06:25

(Math/abs (double (/ 3 -2)))

noisesmith21:06:38

yeah - that's probably a better example

Alex Arslan21:06:17

Am I doing something very obviously stupid here? I'm attempting to port some code I wrote in Racket for computing the Kahan-Babuska-Neumaier algorithm to Clojure, and I get a NullPointerException on line 9:

(ns kahan-sum
  (:use clojure.math.numeric-tower))

(defn sum-kbn [x]
  (letfn [(f [y s c]
            (if (nil? y)
              (- s c)
              (let [xi (first y)
                    t (+ s xi)
                    m (if (>= (abs s) (abs xi))
                        (+ (- s t) xi)
                        (+ (- xi t) s))]
                (recur (rest y) t (- c m)))))]
    (f x 0 0)))

noisesmith22:06:59

FYI since you are explicitly using doubles, you could use Math/abs and skip the numeric-tower dep

noisesmith22:06:16

unless of course you need to support ratios / bignums

Alex Arslan22:06:41

I'm only explicitly using doubles in a particular test case; I'm attempting to be as generic as possible

noisesmith22:06:17

so you do want to support ratios and bignums while preserving type? OK

andy.fingerhut02:06:16

@ararslan I did some performance testing of your original version vs. one with a few explicit calls to the function double, which helps the Clojure compiler determine that it can use primitive 64-bit double values in Java, rather than "boxed" java.lang.Double objects, and gives a significant speed boost to your code on long sequences.

andy.fingerhut02:06:02

Here is the faster version, nearly identical to what you had:

(defn sum-kbn [x]
  (letfn [(f [y ^double s ^double c]
            (if (seq y)
              (let [xi (double (first y))
                    t (double (+ s xi))
                    m (double (if (>= (Math/abs s) (Math/abs xi))
                                (+ (- s t) xi)
                                (+ (- xi t) s)))]
                (recur (rest y) t (- c m)))
              (- s c)))]
    (f x 0 0)))

Alex Arslan14:06:11

Very nice, thanks!

Alex Arslan21:06:39

This is pretty much day 1 for me of trying out Clojure, so perhaps this code is nonsense anyway

hiredman22:06:53

you shouldn't use nil?, use seq and swap the conditions

hiredman22:06:40

(you are getting an empty seq, which isn't nil)

Alex Arslan22:06:14

Ah, actually switching it to empty? works

hiredman22:06:26

also I don't know if :use is officially "deprecated", but using it has bout out of favor for many clojure releases now, :require is used more often

1
Alex Arslan22:06:47

Is there a way to use :require to get all provided symbols, as :use does?

Alex Arslan22:06:03

I mean without explicit namespace qualification

seancorfield22:06:18

(:require [... :refer :all]) but that's also frowned on.

seancorfield22:06:53

(:require [... :refer [just the symbols you need]]) if it really makes your code more readable than alias/symbol

Alex Arslan22:06:46

Hm, okay. Thanks

seancorfield22:06:38

Since you are only using abs: (:require [clojure.math.numeric-tower :refer [abs]]) would probably be acceptable to most people.

Alex Arslan22:06:57

To most people? Who would object?

seancorfield22:06:16

People who think math/abs would be clearer 🙂

seancorfield22:06:34

There's a strong preference for aliases so it is always clear where a symbol comes from.

hiredman22:06:44

that is a whole thing, but I think you read the docs for empty? it says to prefer calling seq

ghadi22:06:01

I prefer seq over empty? but yeah people will fight you to the death about it

ghadi22:06:23

> that is a whole thing 😂

Alex Arslan22:06:48

user=> (doc empty?)
-------------------------
clojure.core/empty?
([coll])
  Returns true if coll has no items - same as (not (seq coll)).
  Please use the idiom (seq x) rather than (not (empty? x))

hiredman22:06:32

you could also keep it like you have it, and use next instead of rest

ghadi22:06:32

people claim that empty? is more "literate", but that is baloney imho

hiredman22:06:58

and if you use next you can nil pun if you swap the order of your branches

ghadi22:06:18

^ that's a good call

hiredman22:06:45

but I would use rest+seq because I am set in my ways

Alex Arslan22:06:37

Success!

user=> (reduce + [1.0 1.0e100 1.0 -1.0e100])
0.0
user=> (sum-kbn [1.0 1.0e100 1.0 -1.0e100])
2.0

Alex Arslan22:06:25

Thanks everyone for your guidance

Alex Arslan22:06:51

What I have now is

(ns kahan-sum
  (:require [clojure.math.numeric-tower :refer [abs]]))

(defn sum-kbn [x]
  (letfn [(f [y s c]
            (if (seq y)
              (let [xi (first y)
                    t (+ s xi)
                    m (if (>= (abs s) (abs xi))
                        (+ (- s t) xi)
                        (+ (- xi t) s))]
                (recur (rest y) t (- c m)))
              (- s c)))]
    (f x 0 0)))
which seems to work. Now I should ask: is this reasonably idiomatic?

dpsutton22:06:53

You could use Math/abs and drop the numeric tower if it’s not needed elsewhere

noisesmith22:06:48

I started a thread about this same point above

👍 1
andy.fingerhut22:06:09

It is fairly unusual to use letfn when there is no mutual recursion between functions locally defined within it, I believe. let would work just as well here.

Alex Arslan22:06:42

Then is it (let [f (fn [...] ...?

alexmiller22:06:57

I'd use letfn here

alexmiller22:06:18

or even just make a separate defn

1
alexmiller22:06:25

sum-kbn* or something

noisesmith22:06:45

or a separate arity

dpsutton22:06:51

I like letfn. You could also make another arity and recur there without the scheme style iter fun

alexmiller22:06:19

exposing that arity publicly seems weird

👍 2
Nate Sutton22:06:37

oooh I'd never see letfn before

👀 1
Alex Arslan22:06:03

I stumbled across it when looking for the Clojure version of letrec

Alex Arslan23:06:36

Say I have a collection x. What's the easiest way to get an empty version of x, i.e. a value with the same type as x but empty?

Alex Arslan23:06:24

My b, it's empty

🎉 1
andy.fingerhut23:06:19

@ararslan It will not answer questions like that for you, but it might help you browse the doc strings a bit faster, and see function names grouped by category: https://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html (can get there in 2 clicks from here, if this is easier to remember: https://clojure.org/api/cheatsheet )

Alex Arslan23:06:40

Very nice, thanks

seancorfield23:06:19

Also, the REPL has an apropos function that can help find functions for you

user=> (apropos "empty")
(clojure.core/empty clojure.core/empty? clojure.core/not-empty clojure.spec.gen.alpha/not-empty)
user=> 

👍 1
andy.fingerhut23:06:32

and of course you are always welcome to ask here, too

❤️ 1
seancorfield23:06:29

There's also find-doc (but it shows docstrings and tends to find a lot more functions -- because it searches both name and docstrings):

user=> (find-doc "empty collection")
-------------------------
clojure.core/empty
([coll])
  Returns an empty collection of the same category as coll, or nil
nil
user=> 

👍 1
seancorfield23:06:00

(`(find-doc "empty")` found way too many functions to be useful here -- unless you don't mind scrolling through pages and pages of them! 🙂 )

😄 1