This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-05-22
Channels
- # announcements (17)
- # beginners (11)
- # biff (5)
- # calva (22)
- # cider (30)
- # clj-kondo (33)
- # clj-on-windows (20)
- # clojure (59)
- # clojure-dev (25)
- # clojure-europe (31)
- # clojure-nl (1)
- # clojure-norway (13)
- # clojure-sweden (5)
- # clojure-uk (6)
- # clojurescript (5)
- # community-development (2)
- # cursive (4)
- # datahike (5)
- # datalevin (7)
- # datomic (11)
- # emacs (8)
- # events (1)
- # gratitude (1)
- # hoplon (5)
- # hyperfiddle (1)
- # lsp (59)
- # matrix (11)
- # polylith (14)
- # portal (3)
- # practicalli (1)
- # rdf (2)
- # reitit (9)
- # releases (3)
- # rum (5)
- # yamlscript (6)
Hey folks, is there a generally accepted guide on idiomatic clojure patterns? I want to understand when and where to use clojure constructs, their anti-patterns etc.
https://guide.clojure.style/ is pretty low-level but covers a few idiomatic constructs. There aren't really many "pattern" level guidelines for Clojure... On http://clojure.org there are some cautions in the sequences/collections sections I think. There's Sierra's old series of Clojure Do's and Don'ts which is mostly still applicable: https://stuartsierra.com/tag/dos-and-donts
@U1Z5X06NP’s blog has some useful stuff. There’s a talk also : https://www.youtube.com/watch?v=hYSxlN_d2-g&t=1s
Here are a few blog posts that might be pertinent: https://blog.janetacarr.com/model-view-controller-a-classic-architectural-pattern-in-clojure/ https://blog.janetacarr.com/software-design-patterns-in-clojure/ https://blog.janetacarr.com/fix-your-clojure-code-clojure-comes-with-design-patterns-part-2/
There’s another that @U051FEYUV wrote a long time ago - https://mishadoff.com/blog/clojure-design-patterns/
https://elementsofclojure.com/ I enjoyed reading Elements of Clojure for a more philosophical take on the topic, but it definitely feels like one of those texts that you have to reread periodically to re-grasp its point of view
another good one, only about when to use which features for polymorphism https://www.amazon.com/Clojure-Polymorphism-Paul-Stadig-ebook/dp/B01LB7NU34
My colleague have written a very strange code.
There are two problems.
The compiler interprets a form (xyz/.x obj)
just like (.x obj)
, and it does the same way inside reify
.
I.e. the compiler just uses (name symbol)
even the symbol is fully qualified.
Could the compiler throws on this code?
(ns foo)
(defprotocol Foo
(x [this]))
(ns bar
(:require
[foo]))
(let [f (reify foo/Foo
(x [_] :ok))]
[(foo/.x f)
(xyz/.x f)]) ;; any ns works
;; => [:ok :ok]
(let [f (reify foo/Foo
(foo/x [_] :ok))]
(foo/x f))
;; => :ok
(let [f (reify foo/Foo
(xyz/x [_] :ok))] ;; any ns works
(foo/x f))
;; => :ok
Tested on clojure “1.12.0-alpha5” and “1.11.0"The behavior changed substantially in the latest versions of 1.12.0-alpha. Behavior as of 1.12.0-alpha11: defining
(defprotocol Foo (x [self]))
after this...
(reify Foo
(x [self]
(not-a-ns/.x self)))
throws with message No such namespace: not-a-ns
;
(reify Foo
(x [self]
(clojure.core/.x self)))
throws with message No such var: clojure.core/.x
;
and finally
(let [a-foo (reify Foo
(x [self]
(println "hello")
(Class/.x self)))]
(x a-foo))
warns-on-reflection that call to method x on java.lang.Class can't be resolved (no such method)
and when evaluating looks up both calls to .x
correctly
(in a-foo
's class the first time and in Class
the second time). However, the error message when Class/.x
isn't found at runtime is No matching method x found taking 0 args for class user$eval11210$reify__11211
, which incorrectly makes it look like the method was looked up in a-foo
's class.
E: formatting.
This will be a huge breaking change for the agencies around the globe (and possibly elsewhere) that have been communicating via messages encoded in nonsense qualifications of dot-form symbols.
that is code that shouldn't work but does by accident and will break in 1.12. i recommend you get your coworker to stop writing code like that
The way this “worked” previously is that the dotted form was macroexpanded and the namespace part of the symbol was ignored in that process. There are lots of macros in the wild that are sloppy about quoting method symbols and make things like this (we recently fixed with-open
). In 1.12 that symbol namespace is no longer ignored as that is now syntax for a qualified instance method.
Re “incorrectly makes it look like the method was looked up in a-foo
’s class”, I think you are misreading that - it’s trying to invoke instance method x in Class and the the class it’s reporting is that of the target object (self).
So I think that is correct
There are really two errors at that point - self is not a Class and Class does not have an X method
It’s reporting the latter, but maybe the former is more relevant
The issue is that the message is that what's actually reported is that the method couldn't be found in the target's class, which is plainly not where the method is looked up in the first place.
Take for instance:
(Object/.add (java.util.ArrayList.) 1)
which reports the rather nonsensical
; Execution error (IllegalArgumentException) at user/eval7745 (REPL:26).
; No matching method add found taking 1 args for class java.util.ArrayList
whereas
(java.util.List/.add (java.util.ArrayList.) 1)
works fine.agreed that this could be better
we'll log an issue for this and try to get it fixed before 1.12 release
What's interesting to note is that due to Java methods effectively only matching by name, the following is a thing:
(load-java
["package user; class DefinitelyNotAList { public boolean add(Object x) { return true; } }"])
nil
clj꞉user꞉>
(doto (java.util.ArrayList.) (user.DefinitelyNotAList/.add 1))
[1]
I imagine the semantic we're looking for here is "find method in target class that overrides a matching one in ns class", so isn't the fix as simple as emitting a cast before lookup?
should already have one
which version of Clojure are you on?
In latest, I see:
user=> (doto (java.util.ArrayList.) (user.DefinitelyNotAList/.add 1))
Execution error (ClassCastException) at user/eval7327 (REPL:1).
java.util.ArrayList cannot be cast to user.DefinitelyNotAList
the error above is in the reporting - it is looking for the add method in the java.util.List class, just reporting the class of the target type
I'm on 1.12.11-alpha BTW:
(definterface IDefinitelyNotAList (^boolean add [^Object x]) (^boolean add [^Object x ^Integer y]))
user.IDefinitelyNotAList
cl:user>
(doto (java.util.ArrayList.) (user.IDefinitelyNotAList/.add 1))
; Execution error (ClassCastException) at user/eval7778 (REPL:72).
; java.util.ArrayList cannot be cast to user.IDefinitelyNotAList
clj꞉user꞉> (deftype DefinitelyNotAListAgain [] IDefinitelyNotAList)
user.DefinitelyNotAListAgain
clj꞉user꞉>
(DefinitelyNotAListAgain/.add (java.util.ArrayList.) 1)
; Execution error (ClassCastException) at user/eval7789 (REPL:10).
; java.util.ArrayList cannot be cast to user.IDefinitelyNotAList
guess that teaches me not to involve my janky pastime stuff in thishey @mikfjaqburcmqtymxb, is load-java
just for the example or is this something you've written? And if you wrote it, would you be able to share it?
something that I wrote for fun and the first thing that I thought of to make up a class with a .add method which isn't a j.u.List really, I should've defined an interface lol
@UEENNMX0T Looks very similar to https://github.com/tailrecursion/javastar, so something that like that is already public. :) But it's old and depends on some things you actually don't need to implement such compilation, especially if you wrap the functionality in a class on your own, so you can implement the same thing as what javastar does, in a similar way, in around 50 lines.
FWIW, the problem there was apparently caused by my implementation happily compiling a non-`public` class, i.e. this works as intended:
(load-java
["package user;
public class A {
public boolean add(Object x) {
return true;
}
}"])
nil
clj꞉user꞉>
(user.A/.add (java.util.ArrayList.) 1)
; Execution error (ClassCastException) at user/eval5788 (REPL:156).
; java.util.ArrayList cannot be cast to user.A
You should post load-java
some place! it would be cool to compare it with other java loading ideas such as javastar
Anyone can comment on :aot :all
vs :aot [my.main.ns]
? Would seem similar for a vanilla-structured project, perhaps :all
would seem nicer to me as it would be more comprehensive and deterministic? i.e. don't leave un-AOTed namespaces.
(Please take as a given that I need AOT in production - it's there for reasons)
There's this passage https://github.com/technomancy/leiningen/blob/24fb93936133bd7fc30c393c127e9e69bb5f2392/sample.project.clj#L272-L273 which isn't super specific, I'd also be curious about additional considerations.
the class files compilation produces are sensitive to the order in which code is loaded, so if the order in which you load code for aot compilation is different from the order in which you load code at runtime you can have problems
and I believe :aot :all
in lein doesn't try to determine dependency orderings between namespaces, so it is very easy to end up with stuff happening in a different order
the corresponding stuff in tools-build builds a dependenecy graph and topo sorts it to determine load order for aot compilaion, but I don't believe that is infallible either
I forget, interopy stuff like protocols and deftypes can get weird, I think there is a call to eval in tools.logging that can go sideways
a big part of the reason aot compilation is sensitive to ordering doesn't have anything to do with the code itself, but the way clojure.core/compile is implemented
it does effectively a forced reload of the namespace you pass it, so if you aot compile by (run! compile nses)
which nses are not in dependency order you can end up with weird double loading / compilation happening
a quick dirty solution is to create a dummy file that requires all your namespaces (in any particular order) and load that file with *compile-files*
bound to true
the potential issue with that is because it avoids the forced reload, anything that is already loaded won't get aot compiled, so if you have a user.clj or complicated tooling that loads stuff before you get a chance to load the dummy file, it breaks
This may help in some cases: https://github.com/active-group/lein-aot-order
Thanks! I wouldn't get too AOP (ahead of problems) with AOT - at the moment I just wanted to understand the tradeoffs. Using tools-build would seem a good improvement - I enjoy Lein for many aspects but here I'd enjoy something precise and modern.
Since AOT is viral, it's usually sufficient (if you want AOT, that is) to list only your program's main-entrypoint ns for AOT'ing and invoke "lein uberjar" to trigger the compilation, which will naturally get the namespaces in an OK order by virtue of Clojure fulfilling the main ns form by loading the referenced things.
I'm looking for something to help with talking to Bluetooth devices. First from a Linux desktop, later from a Raspberry Pi. There's clearly no Clojure lib for this. But even looking at Java libs it's very confusing. com.github.hypfvieh/bluez-dbus
seems like the best candidate I can find, but I can't find any documentation at all to go with it. TinyB
's repo has been archived. Have I missed something?
if you are only trying to do really simple things with bluetooth devices (like read simple values from BLE services running on a microcontroller), you can get by with shelling out to bluetoothctl
https://gist.github.com/SingingBush/50c3fba41ec0642a73b842e8d5802e9d kinda surprised something hasn't emerged out of Android but the bluetooth support in Android might be at the OS/Kernel level rather than in Java
JNI is probably the only way
there is this, which I'm not sure if you saw already, but it did have a release last year and an example application: https://github.com/weliem/blessed-bluez
two things: 1. the new java native stuff is way better than jni 2. bluez does everything over dbus so you don't need jni to do bluetooth stuff but likely need it for doing dbus client stuff
I haven't done it, and when I tried it was with sbcl not clojure, but the wall I ran up against was the native dbus library I was trying to use (in order to interact with bluez over dbus) was sd-bus (systemd's dbus client library) and sd-bus's interface has a lot of preprocessor macros, which are painful(impossible?) to deal with in an ffi context
@UFTRLDZEW I saw that but hadn't dug in yet because it's not in Maven central. It's in some other repo. And then dinner happened. And now patch night is happening. So ... tomorrow 😄
I've seen there might be NodeJS support, so I'm wondering if nbb
could do the job. But I haven't worked with Node very much so that'll be a can of worms.
It seems like Python has great support for BT (which makes this all the more disappointing) so maybe Basilisp is worth a look. At least I'm familiar with Python.
you could also try libpython-clj
but that's a pretty heavyweight dependency, especially if your eventual goal is to do this on a Raspberry Pi. Dunno if Lua/Fennel might also be an effective solution for the necessary C interop