This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-09-14
Channels
- # announcements (40)
- # aws (9)
- # babashka (21)
- # beginners (75)
- # calva (56)
- # chlorine-clover (1)
- # cider (12)
- # circleci (1)
- # clj-kondo (7)
- # cljsrn (13)
- # clojars (3)
- # clojure (171)
- # clojure-dev (11)
- # clojure-europe (64)
- # clojure-nl (11)
- # clojure-spec (6)
- # clojure-uk (9)
- # clojurescript (31)
- # conjure (1)
- # cursive (7)
- # datascript (7)
- # datomic (9)
- # emacs (4)
- # fulcro (65)
- # introduce-yourself (1)
- # jobs-discuss (7)
- # kaocha (7)
- # lsp (39)
- # missionary (5)
- # off-topic (54)
- # pathom (10)
- # re-frame (6)
- # shadow-cljs (110)
- # tools-deps (41)
Is there a way, inside a macro, to get type information about a local reference (that is, from &env
). I'm trying to write some code w/ maximum efficiency, and I need to invoke .toString():
(decompile
(let [a 6]
(.toString a)))
// Decompiling class: com/walmartlabs/lang/string$fn__5082
package com.walmartlabs.lang;
import clojure.lang.*;
public final class string$fn__5082 extends AFunction
{
public static Object invokeStatic() {
final long a = 6L;
return Reflector.invokeNoArgInstanceMember(Numbers.num(a), "toString", false);
}
@Override
public Object invoke() {
return invokeStatic();
}
}
=> nil
Here, Clojure knows that a
is a long, but to invoke .toString() it passes it Number.num()
and then, for some reason, the compiler uses reflection to invoke toString().
Ideally, I could special case a type of java.lang.Long and invoke Long.toString(long) and get maximum efficiency code.
I don’t trust the decompiler. Did you look at the bytecode?
Decompilation is inherently lossy and you should never trust that it’s telling you the real truth
I'll check the decompiler, but I also leveraged LocalBinding/getJavaClass() which let me generate the correct code.
FWIW:
(disassemble
(let [a 6]
(.toString a)))
// Decompiling class: user$fn__10124
class user$fn__10124
Minor version: 0
Major version: 52
Flags: PUBLIC, FINAL, SUPER
public void <init>();
Flags: PUBLIC
Code:
linenumber 2
0: aload_0
1: invokespecial clojure/lang/AFunction.<init>:()V
4: return
public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 2
0: ldc2_w 6
3: lstore_0 /* a */
4: lload_0 /* a */
5: invokestatic clojure/lang/Numbers.num:(J)Ljava/lang/Number;
linenumber 3
8: ldc "toString"
10: iconst_0
11: invokestatic clojure/lang/Reflector.invokeNoArgInstanceMember:(Ljava/lang/Object;Ljava/lang/String;Z)Ljava/lang/Object;
14: areturn
public java.lang.Object invoke();
Flags: PUBLIC
Code:
linenumber 2
0: invokestatic user$fn__10124.invokeStatic:()Ljava/lang/Object;
3: areturn
static {};
Flags: PUBLIC, STATIC
Code:
linenumber 2
0: return
=> nil
Mostly, I've seen decompilers get tripped up by locals clearing. I'm liking using the decompiler to ensure I'm not generating code that uses reflection deep in my macro.what's your actual code where you're having a problem?
It's app code that I can't share currently. I'm working on a limited, but fast macro replacement for clojure.core/format. Shaving those microseconds!
well, I'm doubtful this is a good idea
Hey guys. I want to know if there is a way to not use reflection and use with-open
when you have java.lang.AutoCloseable
and java.lang.Closeable
.
Like this:
(with-open [my-closeable (create-new-closeable)])
Are there any ways to not have this error?
Reflection warning, /my_test.clj:_:_ - reference to field close can't be resolved.
This is a var in project.clj
but I want to not remove it: :global-vars {*warn-on-reflection* true}
Great! It worked! I was expecting to add type hint but I didn't think it could work 😄 Thanks a lot
sdkman or jenv spring to mind
Ah funny introduction: > jEnv is a command line tool to help you forget how to set the JAVA_HOME environment variable I would rather keep that knowledge, thank you very much. :)
why janky?
There's also Jabba (https://github.com/shyiko/jabba), which I've used with some success.
@U04V4KLKC I had to remove certain jdk versions from jenv in order to get them to show up in jenv, even though I had just installed jenv. Also you don’t necessarily pick by the specific jdk distribution, you can pick by distribution(if that’s the right word for it), for example oracle-jdk16 or openjdk16, AND by the version (16, 11, 8, etc ) regardless of the distribution. That made things a bit confusing. Now maybe it’s because my shell is fish, and I’m seeing they’ve had problems implementing jenv so it works properly with fish.
$ jenv versions
system
1.8
1.8.0.302
11
11.0
11.0.12
16
16.0
16.0.1
16.0.2
graalvm64-11.0.10
graalvm64-16.0.1
openjdk64-1.8.0.302
openjdk64-11.0.12
* openjdk64-16.0.2 (set by /Users/main/.jenv/version)
My team mates use http://asdf-vm.com for both java and node
I use nix shell https://nix.dev/tutorials/declarative-and-reproducible-developer-environments
I used to use jenv but it makes your shell slow as molasses, these days I just have a bunch of aliases like this in my zshrc:
java14() {
export JAVA_HOME='/Library/Java/JavaVirtualMachines/jdk-14.0.2.jdk/Contents/Home'
}
I should start doing that as well, I'm using jenv but it doesn't always work well for me
Just out of interest - what kind of scenarios prompt frequent changes to the Java version that you use?
I understand the case with Python - it doesn't have .m2
that it could use, so you kinda have to have such a mechanism.
I know you can set properties for this, but even then it's not guaranteed that some things aren't being pulled in that don't work on 8
You work on a lot of tools, so you're somewhat of an outlier. :) But what about "regular" app development? Just making sure I don't have a blind spot here.
At my work I regularly switch between projects with different java version requirements. Many are still on java 8 because of dependencies, while others require java >=11 (e.g. because of java.net.http
). I use jenv with .java-version
files in the different projects, and that works well for me.
How can I check if x is type of
in Clojure?
Eh yea. Actually my issue was different. I forgotten how to import inner class 😄
Thank you @U11BV7MTK
Oh wait.
Exception in thread "main" java.lang.IllegalAccessError: failed to access class sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream from class com.vethelp.api_lambda.core$AWSApiGatewayProxy (sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream is in module java.base of loader 'bootstrap'; com.vethe
This is the first time I'm seeing something like this 😮
(instance? JarURLConnection$JarURLInputStream x)
This is what I thought.
However I need to check for the type of this class.
I need to differentiate the JarURLInputStream and regular InputStream
you could check for FilterInputStream if you need that in
field, but other than that its identical to an InputStream & FilterInputStream & Closeable
Actually my bad. I don't need to differentiate between those two
Sorry guys! And thanks for all your help!
Sometimes when working with lots of map forwarding (esp. in ReactJS), I wish clojure had an object construction short-hand like the ES2015 notation { foo, bar } === { foo: foo, bar: bar }
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#new_notations_in_ecmascript_2015
Is there any similar tricks in clojure that let me avoid typing the same name twice? It really helps when doing refactoring
I can probably write myself a macro, but it will probably make my code confusing to anyone else. I was wondering in case there's any idiomatic way
What do you mean by "map forwarding"?
And no, you can't have {a b c}
be turned into {:a a, :b b, :c c}
because map literals require an even number of members even at the read time.
I’ve came across this tweet recently https://twitter.com/lyderichti59/status/1434106220697341958
https://clojureverse.org/t/shortand-clojure-syntax-for-properties-on-hashmaps/1918/21
@U2FRKM4TW Basically, take a large map with many fields, add a few fields, delete a few fields, change a few fields, and send the result to another function
All that is trivially solved by core functions that work with maps. One extra thing that makes it easier is avoiding assigning long names in small scopes.
Thanks, guys. These suggestions are all currently what I use daily. Just had to wonder whether I missed anything. Guess not.
(-> m
(dissoc :remove :some-keys)
(rename-keys {:rename :key})
(assoc :add-key value))
yeah ... find that much more readable than the es2015 notation@U11BV7MTK I think the tweeted solution @U0CL38MU1 linked is very close to what I imagined, except that it is customized and will appear confusing to other people reading my code.
(defmacro nmap [& xs]
(cons 'hash-map
(interleave (map (comp keyword name) xs) xs)))
(let [a 1, b 2, c 3]
(nmap a b c))
{:c 3, :b 2, :a 1}
i'd go with this onefrom that clojureverse thread. maybe extend it to be aware of vector pairs to allow normal key value pairs.
I would just repeat the key and the symbol. it's not so bad IMO and if you're working on a team it doesn't seem worth it to add the macro
I tend to agree with @U4YGF4NGM and the farthest I would go is probably @U11BV7MTK's suggestion of defining a named macro, in which case at least it's obvious I'm using a macro and their IDE could lead them to the definition.
yeah. and really take the temperature of the team. if everyone is on board, go for it. if not, don't mix and match
In that clojureverse thread, @U04V70XH6 brought up a good point that maybe that ES2015 feature has it's own downside by being a "visual overload" of the braces, not necessarily a net plus for mental mapping
For destructuring you can use {:keys [foo bar]}
, and for checking equality on components you can use rename-keys
and select-keys
. There is no shorthand for object construction though, and you'd need your own macro for it, and even then you couldn't use the {}
syntax because it requires an even number of forms.
Is there any reasonable way to co-mingle tests/code/resources? e.g. instead of the standard project structure:
src/
a/
b.clj
c/
d.clj
test/
a/
b_test.clj
c/
d_test.clj
resources/
foo.txt
bar.csv
Is it possible to do something more like this?
a/
b.clj
b_test.clj
resources/
foo.txt
c/
d.clj
test/
d_test.clj
resources/
bar.csv
My instinct is that’d require wildcard patterns in path specifications, and I didn’t see anything about those being supportedIt is possible.
For tests - by having the test runner to scan files by a glob/RegEx, so depends on your test runner I suppose.
For resources - by listing the directories out explicitly, assuming you want for them to be on the classpath as e.g. foo.txt
and not a/resources/foo.txt
.
But not sure if anybody actually does that, and FWIW I would certainly not like to find such a structure in a library I need to read or, let alone, support.
If a
and c
are isolated that much, then perhaps it makes sense to split them into different projects?
this ☝️ bundling test code, also means pulling in test dependencies (if there are any) or removing test code before building the jar - so yeah, a bit... unorthodox approach
Not necessarily - you can bundle the code, but if it's not required anywhere, it won't be evaluated so you won't need the dependencies. But perhaps it depends on the bundling approach - I don't really have experience there.
Cool, so sounds like it’s in the realm of “technically possible, but impractical in practice”
@U2FRKM4TW true, it would kinda work for libraries, but not for applications
happy to see https://github.com/clojure/clojure/commit/f96eef2892eaef55dbbdbf7f3b4b818be06f6ddd, thanks @alexmiller and everyone else involved in making it happen! 🙏👏
if I did my job, that should be a better explanation
so if you are using spec you might have a spec :my.cool.domain/account
where "my.cool.domain" is not a namespace you can load, it's just a useful qualifier for specs
awesome, I'm glad you took into account the case where the namespace is loaded later and has real content.
annoying to test :)
before you could just use the full kw but lots of typing
you can't do this:
(ns foo
(:require [my.cool.domain :as d])) ;; fails - can't load my.cool.domain
you can do this:
(create-ns 'my.cool.domain)
(alias 'd 'my.cool.domain)
but, it's annoying and invisible to many analyzers as it's outside ns
now you can do:
(ns foo
(:require [my.cool.domain :as-alias d])) ;; works! (doesn't load)
::d/account ;; valid
so :as-alias
is like :as
(creates an alias) but doesn't force a load of the namespace
I'm looking forward to replacing this code at work as soon as the new Alpha drops:
(alias 'm (create-ns 'ws.domain.member))
(alias 'mu (create-ns 'ws.domain.member.update))
(alias 'mv (create-ns 'ws.domain.member.validation))
This specific use case is only to have aliases instead of writing full names every time, right?
Yes, the above allows me to write ::m/whatever
instead of :ws.domain.member/whatever
Pretty sure I've got some code that I can replace with update-vals
too.
I'll have to retrain my muscle memory to use update-vals
instead of medley.core/map-vals
(thrilled to do it though!)
This specific use case is only to have aliases instead of writing full names every time, right?
in theory if the namespace has been loaded, you could access vars behind it, but I would think there'd be some potential issues with AOT compilation.
specifically with direct-linking turned on
@djblue Not sure what you're asking there... :as-alias
is a way to create aliases for namespaces without needing to have code behind the actual namespace name.
It's useful for keyword resolution, like ::m/whatever
based on the alias
calls above, but you couldn't access any code (unless you had a "normal" require for that namespace).
Yeah, sometimes I want to wait to load a large dep until I need it. But I'm assuming I would still need to use requiring-resolve
. So I could do (requiring-resolve
m/whatever)`
nothing here changes that
if you want to use vars, you have to load
techniques exist for dynamic loading and this does not give you anything new in that regard
But now I can use m/whatever` instead of
the.full.m/whatever` when passing a symbol to
requiring-resolve
seems odd that you'd need to write that in code
isn't requiring-resolve usually just used for user input for command line utilities and similar?
cool that you found a usecase, but it seems odd
I think I'd rather just type it out to make it easier to find :)
Just replaced a pair of nested reduce-kv
calls with update-keys
and update-vals
🙂
Checking out the commit, can't :as-alias result in namespaces being misteriously empty? e.g. ns x requires y under :as-alias and ns z requires y normally, and also requiring x normally i.e. there's the assumption (but not enforcement) that namespace y isn't backed by an actual file? If I put actual contents under it, those can get hidden by an unlucky loading order
It's easy enough to try it out, to convince yourself it works "as expected".
If you look at the commit, I think it specifically has a test for a ns that is required :as-alias
followed by a later (regular) require of that same ns, to verify the code is loaded at that point.
If you load the namespace it will define the vars in the existing runtime namespace
So the rules haven’t changed - if you want to use vars from the namespace, load it
Thanks again. I was thinking, I'm pretty used to use find-ns
to determine whether a ns has been loaded:
https://github.com/jonase/eastwood/blob/8a6655f592004a7d6e028380144cb1210bb7a9f5/src/eastwood/util.clj#L1144
https://github.com/clojure-emacs/refactor-nrepl/blob/1b646079ef3e3c4438c3eeb20514e4d2731f444e/src/refactor_nrepl/core.clj#L338
Looks like I should change this code to use *loaded-libs*
instead
(edit: the linked code in particular wouldn't necessarily be wrong today... I simply used it to reflect how I'm using this pattern in the wild)
I haven’t delved into the polymorphic functionality in Clojure.
For the most part, I have only built applications using pure functions.
When is it helpful to use things like defrecord
, defprotocol
, deftype
, defmulti
, defmethod
, reify
, make-hierarchy
, derive
, etc.?
This is a nice overview: https://github.com/plumatic/eng-practices/blob/master/clojure/20130926-data-representation.md
Here be opinions, but: They’re for open dynamic dispatch (i.e. you have a bunch of different functions, and you need to figure out which one to call.)
They’re great for libraries. They should be used often in libraries.
End-user applications (i.e. what most people write) don’t actually need them very often. In most cases, you’d be fine using a lookup map (e.g. {:foo (fn …), :bar (fn …)}
)
Reason being: in end-user applications, you have total control over all the code. You don’t need open dispatch. Closed is fine.
I’m actually writing a blog post about this. Would you like me to give a shoutout to you guys? @U2FRKM4TW @U07S8JGF7
Probably worth mentioning that the plumatic article predates protocols being extended via metadata -- so the need for defrecord
is (even) less now since plain ol' Clojure data with metadata can satisfy a protocol.
@U04V70XH6 Thank you for mentioning that. I forgot metadata was a thing.
That’d be awesome @U01KQ9EGU79. Thanks for asking.
Where does this concept of open dispatch/closed dispatch come from? Are there more resources that you can point me to? @U07S8JGF7
open = you can add dispatch entries without altering existing code closed = you must alter existing code to add dispatch entries
To be concrete:
defprotocol
and defmulti
both define dynamic dispatch functions. You add to their lookup tables via extend-protocol
, extend-type
, defrecord
, and defmethod
. You can call those macros in any namespace and add to their lookup tables at any time.
If you use a map like I suggested (`{:foo (fn …), :bar (fn …)}`), you have to go to that specific map in the specific namespace, add a new entry, and reload that code.
@U01KQ9EGU79 Does that make more sense?
Method calls on objects are just indirection to functions. Vars are layers of indirection to functions.
I guess I’ve never had to make a map of functions before, or map types to functions.
I’ve never used this pattern, {:foo (fn …), :bar (fn …)}
.
How familiar are you with polymorphism from OOP? (e.g. Java interfaces, JavaScript prototypes, etc)
I do not come from an OOP background at all. Clojure was my first real language and before that I played with python and couldn’t wrap my head around objects then.
My understanding stopped at “Ok, methods are just functions, and objects are just variables with variables and functions inside.” I had no idea why it would be useful to use objects rather than just using functions and variables.
(def ^:private table-columns
(into []
(for [each schema/table-schema]
{:fx/type :table-column
:text (:column-name each)
:cell-value-factory identity
:cell-factory {:fx/cell-type :table-cell
:describe (fn [x]
{:text ((:file-data-key each) x)})}})))
(def table-schema
;; This vector of maps generates the table.
;; `:name` is the string that will show at the top of the column.
;; `:key` is the keyword that will be used in `file-data` to pair with its value.
;; `:fn` is the function that will execute on each value within each kv-pair in `file-data`.
[{:column-name "Full Path"
:file-data-key :full-path
:data-display-fn helpers/full-path}
{:column-name "Movie Name"
:file-data-key :display-name
:data-display-fn display-name}
{:column-name "File Type"
:file-data-key :file-ext
:data-display-fn helpers/extension}
...
])
Now that you mention it, I actually have created a map with functions.
This was my solution for a UI based on cljfx. Could protocols and records simplify this somewhat?Yeah, this is exactly the thing. You’ve built a dispatch table with different implementations of (i.e. different ways of doing) :data-display-fn
.
I personally think that what you have is fine (though I can’t really tell everything you’re using it for).
Hmmmm. Ok, so far I’m only using this lookup table for one thing, a table within a window.
I gotta get to bed for now. You can almost certainly get this done with mulitmethods as an exercise, but I doubt the code would be any clearer or better as a result.
(defn map-file-data [dirs]
(mapv
(fn [dir]
(into {}
(for [schema-entry schema/table-schema]
[(:file-data-key schema-entry) ((:data-display-fn schema-entry) dir)])))
(mapcat file-seq dirs)))
Right, you could def do this with multimethods. Use :file-data-key
as a dispatch fn and provide different implementations for :full-path
:display-name
and :file-ext
.
I’ll try to check back tomorrow and see if you have any questions. Feel free to add on here or DM me in the meantime.
derivation (hierarchies) are great, think having a way to express relations between things without baking the definition of these relations to the values they link. It can be very handy
hierarchies are first class in clojure, there's a global one yes, but you can also create your own and pass them around
@U01KQ9EGU79 It would make more sense to shout-out Plumatic for that article and Sean Corfield for that addendum. :)