Fork me on GitHub
#clojure
<
2022-01-14
>
Sam Ritchie04:01:12

I was looking at an old @cgrand post, http://clj-me.cgrand.net/2010/06/10/primitive-types-support-for-fns-coming-to-a-clojure-branch-near-you/, where he claims that the following code takes 650ms on a 2010 laptop. On my M1, at the REPL, I am seeing 21 seconds! What am I missing?

(defn ^:static fib ^long [^long n]
  (if (>= (long 1) n)
    (long 1)
    (+ (fib (dec n)) (fib (- n (long 2))))))

(time (fib 38))
;; "Elapsed time: 21316.721792 msecs"
;; 63245986

Alex Miller (Clojure team)04:01:33

You don't need the static (does nothing now) or the long coercions (you do need the type hints)

Sam Ritchie04:01:39

woah… 202ms when I type “clj” at the repl. so something weird is happening with my emacs repl

bg04:01:00

clj-test.core=> (time (fib 38))
"Elapsed time: 331.063833 msecs"
63245986
on 13" M1 MBP

💜 1
bg04:01:07

is your emacs compiled for Apple silicon?

Sam Ritchie04:01:46

I get it… I HAPPENED to be in a namespace where I had overwritten the arithmetic operators with the generic multi-method based ones

facepalm 2
Alex Miller (Clojure team)04:01:54

Also, this should actually be faster in the new 1.11.0-alpha4 due to the new use of Math.exact w hotspot intrinsics for the + - dec

❤️ 4
kongra08:01:42

This Math/exact really looks promising:

(defn fib ^long
  [^long n]
  (if (>= 1 n)
    1
    (+ (fib (dec n)) (fib (- n 2)))))

(quick-bench (fib 38))

;; Evaluation count : 6 in 6 samples of 1 calls.
;;              Execution time mean : 304,612157 ms
;; ...

(defn fib ^long
  [^long n]
  (if (>= 1 n)
    1
    (Math/addExact
      (fib (Math/subtractExact n 1))
      (fib (Math/subtractExact n 2)))))

(quick-bench (fib 38))

;; Evaluation count : 6 in 6 samples of 1 calls.
;;              Execution time mean : 236,714805 ms
;; ...

(defn fib ^long
  [^long n]
  (if (>= 1 n)
    1
    (unchecked-add
      (fib (unchecked-dec n))
      (fib (unchecked-subtract n 2)))))

(quick-bench (fib 38))

;; Evaluation count : 6 in 6 samples of 1 calls.
;;              Execution time mean : 201,021006 ms
;; ...

jason poage08:01:09

Am I able to use javascript with clojure? or only java?

Nom Nom Mousse08:01:58

Clojurescript: https://clojurescript.org/ 😄 #clojurescript

simongray08:01:13

I think you need to qualify what you mean by “use javascript with Clojure”.

simongray08:01:41

ClojureScript compiles to JavaScript, Clojure compiles to Java bytecode. It is essentially the same language.

jason poage09:01:35

I have javascript. So I might have two options. Convert javascript to java, or rewrite the javascript in clojure. I’ve never written Java, only javascript. Either direction I go, it will probably not take me much time because my project is still very small. it will probably just make more sense to write it in clojure since it is so small right now.

simongray10:01:45

You can run JavaScript code from ClojureScript. Why is ClojureScript not an option? Do you need to use a Java library too?

jason poage10:01:24

im not sure if i want to use clojure or clojurescript… im writing for a backend so i figured i should use clojure.

👍 1
simongray11:01:40

Another option might be using a JS interpreter from Clojure such as Nashorn, but perhaps rewriting in Clojure is the best option like you wrote.

rgm16:01:44

Also possible: using Clojurescript on the server by generating to a node.js target and running it via node. I haven't seen a lot of this but if your infrastructure/server skills are already node-flavoured this could be a somewhat gentler on-ramp.

rgm16:01:37

Not a lot of blog post guidance on this direction relative to the advice you can find on JVM server work, but it's do-able.

quoll21:01:23

Because I’ve been using it lately, I’ll comment on the JS engine… Nashorn is deprecated now. If you’re on JVM 15 or later, then you need to use the GraalVM https://www.graalvm.org/reference-manual/polyglot-programming/#start-language-java.

👍 1
Arthur17:01:44

There is a blog post from Nextjournal on how to use JavaScript inside Clojure using Graal’s Polyglot (as pointed out by quoll). If you’re just starting to learn Clojure I wouldn’t suggest going that route just yet though

Nom Nom Mousse09:01:00

I want to use Files/exists from Clojure but I can't get it working:

p
;; => #object[sun.nio.fs.UnixPath 0x17f007b4 "/Users/blah/sorted.bam.bai"]

(java.nio.file.Files/exists p)
;; Syntax error (IllegalArgumentException) compiling . at (*cider-repl code/blah:localhost:54327(clj)*:240:7).
;; No matching method exists found taking 1 args for class java.nio.file.Files

(java.nio.file.Files/exists p (into-array []))
;; Execution error (ClassCastException) at user/eval62166 (form-init13866759365748960699.clj:248).
;; class [Ljava.lang.Object; cannot be cast to class [Ljava.nio.file.LinkOption; ([Ljava.lang.Object; and [Ljava.nio.file.LinkOption; are in module java.base of loader 'bootstrap')
What am I missing?

Nom Nom Mousse09:01:59

https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#exists(java.nio.file.Path,%20java.nio.file.LinkOption...) takes an Enum LinkOption linkOption. Hmmmm... I want to follow symlinks (the default option) so I was hoping that an empty array would be OK.

Nom Nom Mousse09:01:20

(java.nio.file.Files/exists p (into-array [java.nio.file.LinkOption/NOFOLLOW_LINKS]))
;; true
Why does the above work? p refers to a symlink. I guess NOFOLLOW_LINKS means: do not check whether the file pointed to exists. But how do I get Files/exists to follow a symlink? There is no equivalent enum for following links...

Nom Nom Mousse09:01:18

I think I solved it: (java.nio.file.Files/exists p (make-array java.nio.file.LinkOption 0))

Nom Nom Mousse13:01:09

Is it possible to separate commands with ; in clojure.java.shell/sh?

p-himik14:01:16

No. But you can have multiple calls to sh.

Nom Nom Mousse14:01:58

That is what I figured. Thanks!

👍 1
Shantanu Kumar17:01:30

The getting started page https://clojure.org/guides/getting_started#_dependencies doesn't mention Java 17 as a supported version, but the download page https://clojure.org/releases/downloads#_stable_release_1_10_3_mar_4_2021 does. Would be good to have it fixed.

Alex Miller (Clojure team)17:01:16

will do, didn't realize that was copied over there

jpmonettas17:01:56

hi everybody! Shouldn't all this expressions return true ?

(vector? `[1 2])  ;=> true 
(list?   `())     ;=> true
(list?   '(1 2))  ;=> true
(list?   `(1 2))  ;=> false because (type `(1 2)) => clojure.lang.Cons  
(set?    `#{1 2}) ;=> true 
(map?    `{1 2})  ;=> true 

noisesmith17:01:51

list? is weird and wrong

noisesmith17:01:22

to be less jocular about it, list? doesn't do what you expect and usually isn't the predicate you actually want

jpmonettas18:01:27

@noisesmith what predicate should I use to distinguish the things that should be printed out like (...) ?

jpmonettas18:01:50

I mean, I can always do like

#(or (list? %)
     (instance? clojure.lang.Cons %))
but is weird

noisesmith18:01:45

this also applies to the much more common case of lazy-seqs:

(ins)user=> (list? (map inc (range)))
false
(ins)user=> (seq? (map inc (range)))
true

jpmonettas18:01:46

oh makes sense

jpmonettas18:01:54

that always confuses me

quoll21:01:17

Same, so I plotted them out

❤️ 2
quoll21:01:50

The red ellipses indicate the type tests that the functions look for

Alex Miller (Clojure team)19:01:04

sequential? is also more often useful than list?

👍 1
Alex Miller (Clojure team)19:01:25

(if you also want to include vectors)

noisesmith19:01:44

from the examples it looked like differentiating seqs from vectors was part of the design

dpsutton20:01:54

Weird question, but does anyone ever make a jar (possibly AOT compiled) and make a classpath that includes that jar and your normal test directory and test in that way instead of using the regular source tree? Benefits would be faster startup by using the AOT version, and also testing an actual deployable artifact rather than just the source tree that would become the deployable artifact?

noisesmith20:01:34

yes I've done this in CI (for integration tests in particular)

dpsutton20:01:49

did you keep tests in a separate repo or do you do some classpath manipulation or classloader shenanigans? Wondering best way to ensure that the jar’s resources are used rather than the source tree

noisesmith20:01:02

the directory with the tests was added to classpath but not the resource directory - it's the build tool that implicitly adds "src" "test" and "resources" to classpath, clojure itself does not do this

noisesmith20:01:33

if you use java to start the jar, you have to opt in each path (starting with the jar itself)

noisesmith20:01:18

of course if you have code that access the file "resources/foo" it's your code that's in error

dpsutton20:01:16

ah i see. using the clojure cli the src paths are top level. could move them to an alias but that feels a bit weird. Was hoping to start up the process pretty similarly to how we do now clj -X:dev:test and a bunch of custom test runner stuff.

dpsutton20:01:38

I’m thinking of making sure when you (require 'foo) it finds foo.clj or the AOT version in the jar and not in src/foo.clj

noisesmith20:01:39

it's easier to just not use the clojure cli - all you need is some code to bootstrap and run clojure.test right?

dpsutton20:01:00

quite a bit more, custom error reporters, test finders, XML output, etc

noisesmith20:01:12

otherwise, just don't start clj in the root dir of your project, and explicitly add the test path

noisesmith20:01:52

if you need things outside the jar and the test folder to run tests, that could justify a new test-root directory in the project - and you'd run the tests from that subdirectory

noisesmith20:01:06

just brainstorming here, I haven't seen this specific situation

dpsutton20:01:16

yeah. that might be useful. The “easy” way here is to add a new local dep on the jar and dissoc the :paths from the deps.edn file. Then it all just works™. But a new deps.edn and a new root for just the tests might be a way to do the same

noisesmith20:01:27

project/
       |
       - src/
       |
       - resources/
       |
       - test-root/
                 | test/
                 | test-resources/

noisesmith20:01:13

in my experience the less knowledge of tool particulars that your system requires, the less likely someone else makes a change that introduces a regression

Joshua Suskalo20:01:58

So this is something that I'd been annoyed with a while back that I'd love to know if there's been any thought put into it. When working with spec you often make specs for domain objects as maps and they have namespaced keywords as keys. Later if things become performance-critical you can replace maps with records and get a speed boost. Unfortunately records can't take namespaced keywords though which forces a breaking change if you're using spec. Has there been any consideration for extending the functionality of records to permit namespaced keys?

noisesmith20:01:00

my strong suspicion is no, as record fields map 1->1 with static data accessible in instances of a record class, and class fields don't have a concept of namespace

seancorfield20:01:16

If you have a hash map {:foo/bar 13} and you switch to a record, that's a breaking change even without Spec in the mix, isn't it? Since the record would only have bar as a field...

noisesmith20:01:13

I think the question is just about that breaking change to records and spec is mentioned as a motivating factor

noisesmith20:01:31

and it would have to be a breaking change, since it's impossible to make a munge that can't clash (though you could make one that' highly unlikely to clash at the cost of severe ugliness)

seancorfield20:01:26

In the spec, you'd change :req to :req-un and :opt to :opt-un but keep the namespaced keys for the spec names (since the -un versions allow namespaced keys for locating the spec but only use the unqualified key for the field name). So that's less of a change than all your code that pulls fields out of a map... So I guess I don't see spec as being the motivator here really?

noisesmith20:01:55

I take people for their word regarding motivation, but I do agree that it's easier to use spec differently than it is to make namespaces work with record keys

1
noisesmith20:01:59

I skipped some steps in my reasoning above re: munging. I take it as a premise that records need that 1->1 mapping of keyword to static field on the resulting object, and a field can't have a namespace, so the only other option I can see there (without some sort of complex indirection) would be munging the namespace into the field name.

seancorfield20:01:15

Yeah, I think your reasoning is correct.

hiredman20:01:42

Given the way keyword invoking works on records, if you were sufficiently motivated I believe you could make your own thing that used the same inline caches with namespaced keywords

hiredman20:01:45

I guess I don't recall of the compiler skips the cache stuff for namespaced keywords

hiredman21:01:52

yeah

(deftype X [thing]
  clojure.lang.IKeywordLookup
  (getLookupThunk [_ k]
    (case k
      ::foo (reify
              clojure.lang.ILookupThunk
              (get [_ obj] (.-thing ^X obj)))
      nil)))

((fn [] (::foo (->X 1))))

Joshua Suskalo21:01:31

@U04V70XH6 no the motivation is the change from :req to :req-un because all consuming code now has to switch to using non-namespaced keywords, which could potentially be a lot of code, and could potentially be library consumers.

Joshua Suskalo21:01:07

And yes, I was personally considering munging of the field names as the solution to this

Joshua Suskalo21:01:30

Thanks for the suggestion there @U0NCTKEV8