This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-10-31
Channels
- # ai (5)
- # announcements (11)
- # beginners (19)
- # biff (1)
- # calva (8)
- # cider (3)
- # clj-kondo (12)
- # clojure (97)
- # clojure-europe (39)
- # clojure-nl (1)
- # clojure-norway (74)
- # clojure-uk (35)
- # clojurescript (8)
- # component (8)
- # conjure (4)
- # cursive (13)
- # data-science (1)
- # datahike (55)
- # datomic (2)
- # emacs (3)
- # etaoin (6)
- # gratitude (1)
- # hoplon (12)
- # hyperfiddle (54)
- # introduce-yourself (1)
- # lsp (70)
- # missionary (40)
- # music (1)
- # off-topic (79)
- # re-frame (78)
- # releases (4)
- # sql (5)
- # squint (9)
- # tree-sitter (4)
- # xtdb (20)
Is there a way using deps to make your entry point a java function instead of a clojure function? Using -M
expects a namespace, and using -X
it can't seem to find the namespace
-M takes a class
Actually I guess it is a class, so not
It is possible to use java directly and just use the CLI to generate the classpath with -Spath
We're just working around it: realised we could make a clj namespace that wraps the other class' main function
You can also pass a .clj script which can do whatever
To call a Java function via -X
would imply that it can take a single hash map argument and it would be passed a Clojure hash map so, even if you could do it directly, it would be a pretty unusual Java function (unless you were really careful about the command line arguments you passed).
Having a Clojure function as a wrapper seems a much safer approach since you could perform argument validation/adapt the Java function's signature etc.
I'm curious as to your use case @U24QP2D4J?
Playing with compiling a SOAP service into classes with jaxb and calling from clj. Our builds had fun time yesterday when the enonic maven decided to have a hiccup, so looking at options for moving it to a more reliable place. (I'm not entirely across it, a different engineer is looking at options)
I have a question on how to ingest data in reader macros. We have a reader macro #hawk/malli
that will take a malli schema in our tests. We call eval
on the form in the reader macro, but this is leading to problems in resolving functions in clojure core. It seems to be resolving clojure.core/map?
to a new object. It behaves correctly when called, but malli has a registry based on function identity and itâs not finding it because it has a new location in memory. More details in thread
(let [schema #hawk/malli [:map [:k [:maybe map?]]]]
(println {:schema (.schema schema)
:map? map?})
(malli.core/validate (.schema schema)
{:k {}}))
{:schema [:map [:k [:maybe #object[clojure.core$map_QMARK___5489 0x49cfc60c clojure.core$map_QMARK___5489@49cfc60c]]]],
:map? #object[clojure.core$map_QMARK___5489 0xe0847a9 clojure.core$map_QMARK___5489@e0847a9]}
Execution error (ExceptionInfo) at malli.core/-exception (core.cljc:138).
:malli.core/invalid-schema
note that the schema references a clojure.core/map?
at location 0x49cfc60c
whereas map?
is at location 0xe0847a9
so iâm wondering what the proper way to read âcomplicatedâ structures are in data-readers
I noticed that ordered/set #'flatland.ordered.set/into-ordered-set,
seems to sidestep this issue by not evaling itâs arguments, but it leads to some nonsense like
#ordered/set [1 2 (+ 1 3)]
#{1 2 (+ 1 3)}
this is a slightly different way to handle it but could you use the :map
keyword-style of schemas in malli? and then move any custom schemas to the global schema registry?
it is for use in tests. we need some way to dispatch to a malli validation and we have a deftype wrapper. But the fundamental question remains, why do i get a different version of clojure.core/map? and what is the proper way to use data-readers
instead of eval, could you use resolve
? maybe postwalk plus resolve?
i'm not sure why eval
results in different identity for core functions
lol I agree
safe yourself a lot of pain and do not use reader tags in source code. (hawk/malli [:map [:k [:maybe map?]]])
works just fine with no undefined behavior or quirks đ in data this is fine, but for source code you have to deal with a lot of quirks and pain, especially if you want to add AOT compilation
the suggestion from our work slack was
`(->Malli ~form)
seems to work. Again this is just for test code for dispatch in our test matchers. Would you still stay away from data readers there?everywhere in any .clj(c|s)
file, absolutely without question should never contain any reader tags in my view yes
example usage is
(is (=? {"Rome" {:pk_ref (mt/$ids $municipality.id)
:name "Rome"
:model_id (:id model)
:model_name (:name model)
:model_index_id #hawk/malli :int}
"Tromsø" {:pk_ref (mt/$ids $municipality.id)
:name "Tromsø"
:model_id (:id model)
:model_name (:name model)
:model_index_id #hawk/malli :int}}
(into {} (comp relevant (map (juxt :name normalize)))
(search! "rom"))))
so we have our =?
test form which asserts equality and we can mixin #hawk/malli
on a malli schema. Seems like it works with self-evaluating (numbers, keywords, etc) stuffanother example
(is (=? [{:id #hawk/malli Tab-Id-Schema
:name "A look at Reviews" :position 0}
{:id #hawk/malli Tab-Id-Schema
:name "A look at Orders" :position 1}]
(:tabs dash)))
and
(def Tab-Id-Schema
"Schema for tab-ids. Must be integers for the front-end, but negative so we know they do not (yet) exist in the db."
[:fn neg-int?])
same, this is clojure code. so why not just :model_index_id (hawk/malli :int)
? I mean this might work if this is your reader function, just called as a regular function
It sounds sort of like multiple clojure runtimes isolated from each other by class loaders
it seems that
(defn read-malli
"Data reader for `#hawk/malli`."
[schema-form]
`(->Malli ~schema-form))
is a good fix from the older version
(defn read-malli
"Data reader for `#hawk/malli`."
[schema-form]
(->Malli (eval schema-form)))
Iâm quite open to getting rid of reader macros. But first I want to understand the right way to do it and understand why iâm getting a different version of clojure.core/map?iâm wondering if eval
in the reader macro causes a double eval? Iâm a bit scrambled by how many times that gets evaluated
You could take each map?, get the class, and walk up the class loaders for each to see if they are all the same
the value passed to the reader function has been read but not evaluated
if you think eval needs to be involved, I would suggest this is probably a bad usage of tagged literals
have you heard about functions? they are pretty cool and do eval for you :)
haha i agree. i think this was a syntax preference in our test code and we didnât realize some of the tradeoffs involved
the reader value will get evaluated when it's returned so that is likely the source of the double eval
I bet it is because the form you are evaluating has the fn object for map? Embedded in it, not the symbol
probably something like that
if the data reader is meaningful to you, maybe check out the keyword schemas in malli. those will pass through to malli easily
Because the map? doesn't close over anything it gets the generic embed object bytecode treatment of "call no arg constructor of its class"
ok. so for the flatland ordered set reader
ordered/set #'flatland.ordered.set/into-ordered-set,
which has source
(defn into-ordered-set
[items]
(into empty-ordered-set items))
which yields this
#ordered/set [1 2 (+ 1 2)]
#{1 2 (+ 1 2)}
Do you consider this GIGO or is there a proper fix of their function? (perhaps
(into empty-ordered-set ~items)` ?I consider that working as intended and nothing to fix. reader tags are meant for data, not "code yet to be evaluated"
(thank you both. just wanting to fully understand it before jumping into a fix (replacing with raw functions, fixing the reader macro function, only using self-evaluating data in the macro call sites)
Another example:
user=> #inst "2023"
#inst "2023-01-01T00:00:00.000-00:00"
user=> #inst (str 2023)
Syntax error reading source at (REPL:4:17).
they are called "https://clojure.org/reference/reader#tagged_literals" by the documentation
yes. but i was kinda keying off of
#{1 2 (+ 1 2)}
#{1 3 2}
which does the right thing. So was wondering how to do the right thing with code that might be evaluatedAlso echoing @UEENNMX0T - I'd also recommend favoring keyword over functions when defining malli schemas. It just sidesteps some future headaches that may or may not come up.
if you want to make things even more complicated add CLJS into the mix, which handles reader tags in source files entirely different đ
so this is interesting. Double evaling doesnât necessarily give me a new instance. But it does if itâs inside a vector
(let [evaled (eval 'map?)
double (eval evaled)]
{:evaled evaled
:double double
:original map?
:identical? (identical? map? double)})
{:evaled #object[clojure.core$map_QMARK___5489
"0xe0847a9"
"clojure.core$map_QMARK___5489@e0847a9"],
:double #object[clojure.core$map_QMARK___5489
"0xe0847a9"
"clojure.core$map_QMARK___5489@e0847a9"],
:original #object[clojure.core$map_QMARK___5489
"0xe0847a9"
"clojure.core$map_QMARK___5489@e0847a9"],
:identical? true}
here the double
evaled form is identical? to map?
Note all locations are 0xe0847a9
But if we put it inside a vector
(let [evaled (eval '[:stuff map?])
double (eval evaled)]
{:evaled evaled
:double double
:original map?
:identical? (identical? map? (last double))})
{:evaled [:stuff
#object[clojure.core$map_QMARK___5489
"0xe0847a9"
"clojure.core$map_QMARK___5489@e0847a9"]],
:double [:stuff
#object[clojure.core$map_QMARK___5489
"0x506ba0ae"
"clojure.core$map_QMARK___5489@506ba0ae"]],
:original #object[clojure.core$map_QMARK___5489
"0xe0847a9"
"clojure.core$map_QMARK___5489@e0847a9"],
:identical? false}
function identity breaks. location on the single evaled and original are 0x0847a9
(as before) but evaling [:stuff map?]
twice resolves map?
to location 0x506ba0ae
Yeah, you are basically seeing the difference between eval via the interpreter and eval via the compiler
so the tl;dr would be that tagged literals should be reserved for self-evaluating literals. Using the syntax quote solution is working around some of the pitfalls from not using self-evaluating literals but isnât necessarily clear of all of them and there could be other things lurking.
The issue with reader literals and eval is once you start having your reader literals produce non-core collections at read time, which is a popular usage for them, the evaluator doesn't know how to traverse them
% clj -Sdeps '{:deps {org.flatland/ordered {:mvn/version "1.15.11"}}}'
Clojure 1.11.1
user=> (use 'flatland.ordered.set)
nil
user=> (ordered-set 4 3 1 8 2)
#ordered/set (4 3 1 8 2)
user=> #ordered/set (4 3 1 8 2)
#ordered/set (4 3 1 8 2)
user=> #ordered/set (4 3 1 8 (+ 1 2))
#ordered/set (4 3 1 8 (+ 1 2))
user=> (defprotocol IEliminate (-eliminate [obj]))
IEliminate
user=> (extend-protocol IEliminate (type #ordered/set ()) (-eliminate [x] `(ordered-set ~@(seq x))))
nil
user=> (eval #ordered/set (4 3 1 8 (+ 1 2)))
#ordered/set (4 3 1 8 (+ 1 2))
user=> (eval (-eliminate #ordered/set (4 3 1 8 (+ 1 2))))
#ordered/set (4 3 1 8)
user=>
%
if eval would call something like IEliminate internally on unknown objects, you could teach it how to traverse custom collection types and make them behave more like core collections
You could take each map?, get the class, and walk up the class loaders for each to see if they are all the same
Why is it possible to sort this:
(sort [[1 3] [1 2]])
But not this:
(sort ['(1 3) '(1 2)])
presumably boils down to random access. You can access the N
th item in a vector in constant time. For a list you have to walk the list N
times. So if you compared a bunch of things that were long you would have a drastic slowdown in code execution
you can of course build a comparator that behaves this way. so it is âpossibleâ to sort them. But you are forced to be aware of this
Vector comparison is item by item. You don't need random access to be able to implement that with lists.
No clue why lists themselves are not comparable. Perhaps it was never seen as practical, maybe because it's very easy to confuse lists with lazy seqs, and those can be infinite.
of course you donât. but thatâs the default way it is implemented
public int compareTo(Object o){
IPersistentVector v = (IPersistentVector) o;
if(count() < v.count())
return -1;
else if(count() > v.count())
return 1;
for(int i = 0; i < count(); i++)
{
int c = Util.compare(nth(i),v.nth(i));
if(c != 0)
return c;
}
return 0;
}
relying on the constant accessvectorâs compare uses constant time access for count and positional comparisons. Both things lacking for lists
Thanks to you both, very informative discussion, too
How can I conditionally define a fn ? I'm probably missing someting:
;; needed for Clojure 1.10 compatibility
(if (function? (symbol 'clojure.core/parse-long))
(println "parse-long defined")
(defn parse-long [s]
(Long/valueOf s)))
But defn has compile time effects, so you cannot do what you want and with a runtime conditional
;; needed for Clojure 1.10 compatibility
(when-not (resolve 'clojure.core/parse-long)
(intern 'migratus.cli 'parse-long
(fn [s] (Long/valueOf s))))
now I need to test this with clojure 1.10 and see if it worksLong/valueOf isn't entirely complete, parse-long will return nil for an unparsable string instead of throwing an exception
If your goal is just avoiding the warning, then you can either:
⢠rename your function so it doesn't clash anymore
⢠In your ns
form, do this:
(:refer-clojure :exclude [parse-long])
does tools.cli allow me to pass in edn maps as strings? Trying to parse this
./migratus.sh -vvv --config-data "{:store database}" create
gives me :
{:options {:verbosity 3, :config-data {:store}, :arguments [database} create] REDACTED}
which seems wrong.
Does anyone know if this is a bug or a feature?
I might be wrong but "normal" cli parsing (bash at least) should not ignore quotes .Try printing with prn
instead of println
to see the double quotes around strings. I assume that something in that second block is a string because otherwise that data structure isn't readable.
Also, it depends on what you have in your migratus.sh
. You have to be careful with double quotes in scripts - otherwise, you'll end up treating a single argument like "1 2"
as two arguments, 1
and 2
, which is, seemingly, what's going on here.
user=> (require '[clojure.tools.cli :refer [parse-opts]])
nil
user=> (require '[clojure.edn :as edn])
nil
user=> (def cli-options [["-c" "--config-data EDN" "Configuration Data" :default {} :parse-fn edn/read-string]])
#'user/cli-options
user=> (parse-opts ["-c" "{:store database}"] cli-options)
{:options {:config-data {:store database}}, :arguments [], :summary " -c, --config-data EDN {} Configuration Data", :errors nil}
user=>