This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-07-20
Channels
- # announcements (1)
- # babashka (32)
- # beginners (100)
- # cider (43)
- # clj-kondo (4)
- # cljdoc (3)
- # cljs-dev (5)
- # cljsjs (2)
- # cljsrn (22)
- # clojure (170)
- # clojure-australia (27)
- # clojure-europe (25)
- # clojure-nl (3)
- # clojure-uk (76)
- # clojurescript (127)
- # conjure (14)
- # core-matrix (1)
- # cursive (9)
- # datomic (6)
- # defnpodcast (1)
- # emacs (32)
- # events (1)
- # expound (77)
- # fulcro (30)
- # graalvm (21)
- # graalvm-mobile (30)
- # helix (4)
- # honeysql (1)
- # hyperfiddle (1)
- # jackdaw (8)
- # jobs (6)
- # kaocha (1)
- # leiningen (4)
- # lsp (16)
- # malli (46)
- # meander (4)
- # off-topic (19)
- # pathom (10)
- # podcasts-discuss (1)
- # portal (2)
- # re-frame (7)
- # reagent (2)
- # releases (1)
- # remote-jobs (11)
- # rewrite-clj (8)
- # shadow-cljs (9)
- # tools-deps (243)
- # vim (1)
I have defined a macro and used often in my program, called defn-memoized
. The macro essentially takes three things, a public-name, internal-name, and function body which includes the arglist. The macro, defn-memoized
defines a function named by the internal-name using the given function body, and defines a global dynamic variable using the public name. The value of the dynamic variable is a memoized version of the internal function. The advantage is that within a dynamic scope, using binding
, I can rebind the memoized function, and after the binding
form finishes, clojure releases the cache for the function.
I heavily use this in test suites which run 1000s of randomly generated tests-- mitigating the risk of filling up the memoization cache.
That works well.
What does not work is that I have no way of doing this for function which are implemented by defmulti
/`defmethod` .
(defmacro defn-memoized
[[public-name internal-name] docstring & body]
(assert (string? docstring))
`(let []
(declare ~public-name) ;; so that the internal function can call the public function if necessary
(defn ~internal-name ~@body)
(def ~(with-meta public-name {:dynamic true}) ~docstring (gc-friendly-memoize ~internal-name))
))
(defn call-with-compile-env [thunk]
(binding [rte/compile (gc-friendly-memoize rte-to-dfa)
canonicalize-pattern-once (gc-friendly-memoize -canonicalize-pattern-once)
gns/check-disjoint (gc-friendly-memoize gns/-check-disjoint)
]
(thunk)))
(defmacro with-compile-env [[] & body]
`(call-with-compile-env (fn [] ~@body)))
Why does this work for normal functions, but not for multi methods? well I can easily change a defn
to a defn-memoize
without touching any call-site, and without touching any ns
definition require
. I don't see how to do this for methods. because the defmulti
, defmethod
, and call-sites all need to match.
TLDR; how can I memoize a function defined by defmulti
in a way which is invisible to the call-site?
For testing purposes, I think with-redefs
is more useful than binding
. That will bind in more than the current thread, and work for all vars not just dynamic ones.
(defmulti foo :tag)
(defmethod foo :foo [_] :foo)
[(foo {:tag :foo})
(with-redefs [foo (fn [_] :bar)]
(foo {:tag :foo}))] ;; => [:foo :bar]
dynamic binding has a performance cost, so (around 1.2, I think) clojure started enforcing that you marked vars that needed to by bound dynamically with some metadata (`^:dynamic`) to hint to the compiler to not inline that lookup (for example)
but I think in general people often put a defmulti
behind a defn
anyway in the same way people use protocols.
that means you can't make it invisible at the call site because the name at the call site necessarily differs from the name given at defmulti
and defmethod
? right?
wrt with-redefs
vs binding
for testing purposes, don't I want rebinding ONLY in the current thread and threads the current thread starts, which is what binding
promises to do if I'm not mistaken. I don't want two different tests running in two different threads to see the same rebinding.
I guess that depends on what else is going on while you're running your tests. Sure, if you're trying to run multiple things concurrently, and they never need to coordinate this across threads, then maybe binding
fits. But it's a test suite, so I think it's more usual to coordinate the tests so that they don't run concurrently with other processes that would be interfered with. The call site is dereferencing a var and running whatever it finds there, right? I'm not sure I understand the problem.
the problem: if you put a defmulti
behind a defn
then the call-site must correspond to the defn
not to the defmethod
... thus you can't take an existing program which has many calls to functions defined by defmulti
and refactor it so that the function is memoized ... i.e. you'd be forced to invent a new name for the defn
and change all the call sites to use that new name
I think I often consider the dispatching facilities like defmulti
and defprotocol
to be internal things that are only exposed as extension points for "advanced" uses of a lib. So for uses of the lib, it should only have call sites into the public function and if some additional functionality needs to be added to handle a new case, that's an extension point
this is very different from the use case of my defn-memoized
macro. I can take any function defined by defn
and change defn
to def-memoized
without touching any call-site
can't you just extend your macro to put either a defn
or a defmulti
in the private one? You've still created a public fn which is the the main thing that's getting called, right?
something like
(defmacro defmulti-memoized
[[public-name internal-name] dispatch-fn docstring]
(assert (string? docstring))
`(let []
(declare ~public-name) ;; so that the internal function can call the public function if necessary
(defmulti ~internal-name ~dispatch-fn)
(def ~(with-meta public-name {:dynamic true}) ~docstring (gc-friendly-memoize ~internal-name))))
???yes, and all the methods area already named with the public name.
perhaps I need to also define defmethod-memoized
?
Or I suppose refactor all the defmethod
calls to use the internal name.
BTW the terminology seems bizarre to me. according to the documention defmulti
defines a multimethod, and defmethod
installs a method onto a multimethod.
in CL defgeneric
defines a generic function and defmethod
installs a method onto the generic function.
no I want the multimethod to be memoized.
you can't call a method directly without going through the dispatch function ... the multi
in defmulti
is just short for multimethod
... right?
a multimethod is not a special kind of method as the English language prefix implies.
just terminology, and naming is the hardest problem in computer science. I realize that
> no I want the multimethod to be memoized. then I think that the bit of code I posted above will probably do it.... right?
> a multimethod is not a special kind of method as the English language prefix implies. true, but it's also not a cl generic function or a method (as in java) ... which both kindof inform the need to find a new name 😉 ... shrug ... agreed ... naming is hard
BTW in the documentation of defmethod
the name multifn
is used instead of multimethod
ok ... then I'm probably getting the name wrong 😉 ... conceptually, the body of a defmethod
is one branch of the dynamically constructed switch statement inside a defmulti
... right? ... so as long as you register all the methods before calling the memoized fn it should work ... right?
just that as far as clojure is concerned the defmethod and defmulti must have the internal name. and the public name is used at the call site and is just a dynamic variable whose value is the value memoize
(or gc-friendly-memoize
) returns when given the internal name of the defmulti.
I think it will work. I'm trying it out.
I don't think clojure exposes names for the methods. You can get at the implementations by calling methods
sorry, that's not what I mean. I mean that when you type defmethod
you must then give the name of some multimethod
. and that name must all match the name at the call-site, i.e. where the function is called in the application.
I want the call site to name a memoized function. therefore the macro must insert the internal name into the defmethod call and the same name into the defmulti call.
(defmulti-memoized [public-foo internal-foo] ...)
(defmethod-memoized public-foo ...)
(defn xyzzy [x y z]
(public-foo ...))
defmulti-memoized
expands to (defmulti internal-foo ...)
and defmethod-memoized
expands to (defmethod internal-foo ...)
I'm not sure I understand the need for defmethod-memoized
... surely that's just caching the same thing twice?
ah ... I see ... you mean defmethod-memoized
is just mapping the name to the one generated by defmulti-memoized
... sorry ... I'm being slow
if you have to provide the internal-foo when you're defining the memoized multimethod, I'm not sure I see the difference between a search and replace for defmethod foo
-> defmethod-memoized foo
and defmethod foo
-> defmethod foo-internal
yes that's basically what the macro does. good point. if I have to edit all the method definitions anyway, I can just as easily change the function name as change defmethod to defmethod-memoized I suppose.
BTW, I'm curious why what I've written doesn't work.
(defmacro defmulti-memoized
[[public-name internal-name] docstring dispatch-fn]
"Define a multimethod on an internal name, and a memoized function implemented
as a dynamic variable. Methods must be defined using defmethod-memoized, using
the public-name."
(assert (string? docstring))
`(let []
(declare ~public-name) ;; so that the methods can call the public function if necessary
(defmulti ~internal-name ~dispatch-fn)
(def ~(with-meta public-name {:dynamic true :internal-name internal-name :multi true})
~docstring
(gc-friendly-memoize ~internal-name))))
(defmacro defmethod-memoized
[public-name dispatch-val & fn-tail]
(assert ((every-pred :dynamic :multi :internal-name) (meta public-name))
(format "%s does not name a multimethod defined by defmulti-memoized" public-name))
`(defmethod ~(:internal-name (meta public-name)) ~dispatch-val ~@fn-tail))
defmethod-memoized (as I have written it) tries to read the meta data established by defmulti-memoized in order to compute the internal name given the public name.
but meta
returns nil
it seems that (def ^:dynamic xyzzy)
does not put meta data on the symbol, just passes metadata to def
😞
BTW, perhaps an advantage of using the public name in defmethod-memoized
is that it allows the internal name to be as cryptic as I like. And I believe it expresses the intent better. However, my idea might not work if I can't retrieve the meta data to compute the internal name from the public name.
(def ^:dynamic thing ^{:other :thing} {})
(meta thing) ;; => {:other :thing}
(meta #'thing) ;; => {:dynamic true, :line 10 ....}
are you looking for a var-quote?I think I would keep going with the search and replace, and just rename the defmulti / defmethods to a specific internal name (eg foo*
) and then have the public fn foo
implement the memoization, rather than having the macro creating names ...
yes, with defn it is easy to encapsulate, but with defmethod/defmulti it seems difficult.
not very lispy though. the lispy is is to not make the user follow a bunch of programming rules which are automatable. but one has to choose his battles I suppose.
another nice thing about macros, as you already know, is that I may not know at first what the final implementation needs to be. if the logic is centralized into one place I'm free to change it later without a huge refactoring.
if you want to hide it you could look at alter-var-root
... and you might be able to get a single fn that memoizes both a defmulti and a defn and attaches the original implementation as metadata?
I don't think that property is specific to macros .... I think that's achievable with functions too
I think I would prefer to work with the code that has explicit vars for the memoized and non-memoized versions, with the non-memoized ones considered "private" and not for normal consumption ...
(defn- memoize-var [v]
(alter-meta! #'foo assoc :orig/impl (deref v))
(alter-var-root #'foo (fn [f] (m/memoizer f (c/soft-cache-factory {})))))
(memoize-var #'foo)
the problem with macros is that they're only available at compile-time ... so they're not really composable at run-time without compiling more code. So once you're in macroland it's really hard to get back out again ... In my experience it's best to do all the actual work in functions, and only write a very thin macro over the top to handle the syntax part
I’m trying to write the following in clj (http://docs.web3j.io/latest/transactions/transactions_and_smart_contracts/):
Function function = new Function<>(
"functionName", // function we're calling
Arrays.asList(new Type(value), ...), // Parameters to pass as Solidity Types
Arrays.asList(new TypeReference<Type>() {}, ...));
and so far I have this:
222 (Function. "buy"
223 (java.util.Arrays/asList (into-array [str]))
224 (java.util.Arrays/asList (into-array [clojure.reflect/TypeReference])))
225
But I don’t think this is correct. I have many questions. What does new Type(value)
mean? What goes into value
? What is new TypeReference<Type>() {}
, what does it mean to have () {}
after new TypeReference<Type>
, what is this function doing?Off topic? From skimming the docs I get the impression that the value
is the amount of ether you wish to deposit, but I'm obviously not an expert on the library. Might want to post your question on a channel more closely aligned with your interests.
my question is more about the interop; the value
part I might do with trial and error
more specifically the new TypeReference<Type>() {}
part
Currently I’m getting:
No matching ctor found for interface java.util.function.Function
.. which would be the case if you're supposed to use j.u.f.Function
like in the exception you pasted. However I think you really should be using org.web3j.abi.datatypes.Function
instead.
the new TypeReference<Type> () {}
means create an anonymous subclass of TypeReference
with no initialisation code ... see https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html ... I think that the Function
in that code block refers to org.web3j.abi.datatypes.Function
which looks like it does have a constructor
Also, Type is an interface: https://github.com/web3j/web3j/blob/master/abi/src/main/java/org/web3j/abi/datatypes/Type.java
I think I can simply do:
(java.util.Arrays/asList (into-array [java.lang.String]))
rather than:
(java.util.Arrays/asList (into-array [(Type'. java.lang.String)]))
?How do I convert the `new TypeReference<Type> () {}` to clojure?
I tried:
237 (java.util.Arrays/asList (into-array [(proxy
238 [TypeReference] [])]))
but this gives:
“Missing Type Parameter” from here:
https://github.com/web3j/web3j/blob/781ce5019b3f9655c20236644a4940dcfde0ac88/abi/src/main/java/org/web3j/abi/TypeReference.java#L49Clojure vectors implement java.util.List
so you don't need to make an array to turn it into a list : (instance? java.util.List [1 2 3])
It looks like that TypeReference abstract class is using the java reflection api to get around type erasure : https://github.com/web3j/web3j/blob/781ce5019b3f9655c20236644a4940dcfde0ac88/abi/src/main/java/org/web3j/abi/TypeReference.java#L25
so I think you're well into the weeds of java interop ... you'll probably need to at least implement this : https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/ParameterizedType.html with your proxy
yeah I’m just writing the thing in java and calling from my clojure app
Does anyone have an idea of what this error message means? It happens when I evaluate (def rte-inhabited? nil)
Show: Project-Only All
Hide: Clojure Java REPL Tooling Duplicates (18 frames hidden)
2. Unhandled clojure.lang.Compiler$CompilerException
Error compiling src/clojure_rte/rte_construct.clj at (50:1)
#:clojure.error{:phase :compile-syntax-check,
:line 50,
:column 1,
:source
"/Users/jnewton/Repos/clojure-rte/src/clojure_rte/rte_construct.clj",
:symbol def}
Compiler.java: 7114 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 6745 clojure.lang.Compiler/analyze
Compiler.java: 7180 clojure.lang.Compiler/eval
Compiler.java: 7131 clojure.lang.Compiler/eval
core.clj: 3214 clojure.core/eval
core.clj: 3210 clojure.core/eval
interruptible_eval.clj: 87 nrepl.middleware.interruptible-eval/evaluate/fn/fn
AFn.java: 152 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
core.clj: 665 clojure.core/apply
core.clj: 1973 clojure.core/with-bindings*
core.clj: 1973 clojure.core/with-bindings*
RestFn.java: 425 clojure.lang.RestFn/invoke
interruptible_eval.clj: 87 nrepl.middleware.interruptible-eval/evaluate/fn
main.clj: 414 clojure.main/repl/read-eval-print/fn
main.clj: 414 clojure.main/repl/read-eval-print
main.clj: 435 clojure.main/repl/fn
main.clj: 435 clojure.main/repl
main.clj: 345 clojure.main/repl
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 84 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 152 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
AFn.java: 22 clojure.lang.AFn/run
session.clj: 202 nrepl.middleware.session/session-exec/main-loop/fn
session.clj: 201 nrepl.middleware.session/session-exec/main-loop
AFn.java: 22 clojure.lang.AFn/run
Thread.java: 832 java.lang.Thread/run
1. Caused by java.lang.NullPointerException
Cannot invoke "clojure.lang.IPersistentMap.valAt(Object)" because
the return value of "clojure.lang.Var.meta()" is null
Var.java: 248 clojure.lang.Var/isMacro
Compiler.java: 7496 clojure.lang.Compiler/lookupVar
Compiler.java: 7501 clojure.lang.Compiler/lookupVar
Compiler.java: 544 clojure.lang.Compiler$DefExpr$Parser/parse
Compiler.java: 7106 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 6745 clojure.lang.Compiler/analyze
Compiler.java: 7180 clojure.lang.Compiler/eval
Compiler.java: 7131 clojure.lang.Compiler/eval
core.clj: 3214 clojure.core/eval
core.clj: 3210 clojure.core/eval
interruptible_eval.clj: 87 nrepl.middleware.interruptible-eval/evaluate/fn/fn
AFn.java: 152 clojure.lang.AFn/applyToHelper
maybe cider is confused. I'll restart. but this will be a headache because I know the code isn't working yet 😞
Do you call reset-meta!
anywhere? Perhaps you used nil
as its second argument, something like (reset-meta! #'rte-inhabited? nil)
.
yes I did. I was trying to figure out the difference between reset-meta! and something-else-meta!
Well there you have it. :) Metadata cannot be nil
. If you want to reset it, use an empty map instead.
if metadata cannot be nil, then why does reset-meta! allow such?
that's curious
Just a lack of the check. You can find a lot of such things in Clojure. Or rather, not find. :)
Does anyone know if there’s an epub or mobi version of the Clojure docs out there? My searches are coming up short.
not to my knowledge
Figured as much. Just rooted an old nook reader and thought it might be a nice way to study clojure offline.
If you're using Cursive, it can download Clojure docs and show them on the Quick documentation
action.
If you're interestd in a forthcoming book about pretty much all of clojure and the standard library, you wouldn'tgo far wrong with this
Hi there, very newbie question here - is require
composing namespaces with same names ... can I have multiple my-ns.foo
with different vars in them?
This is very handy when you want to "add vars" for development with namespaces stored in src/dev
- for instance - for some reason I have always thought that would work but it does not seem to
Ok, well you basically end up needing to differentiate using the namespace name (say my-ns.dev.foo
) while you could have just used the classpath mechanism (`-A:dev`) for it and let the runtime compose.
I guess you would end up with what Java is doing (and conflicts) so I can see why this was chosen not to be the case.
there is a single namespace registry per clojure runtime, and it is a flat map (no trees) of namespace names to namespace objects
you can run multiple clojure runtimes in a single jvm, which a lot of building tooling will often do, but it is an involved process, and communicating between the runtimes is usually painful
Mind elaborating what multiple Clojure runtimes mean? Can these supposed runtimes really be isolated from each other?
you do some classloader magic to isolate the loaded classes, which causes the jvm to treat them as distinct and different types
on the jvm it often looks like what identifies a type is its name, but it is actually the pair of the name and the classloader that loaded in the class, and in most cases classloaders will delegate loading to their parent, so you usually get the same class (same name and loader) for a given class, but if you break that delegation chain you can load a class multiple times with the same name and the jvm will treat it as a different type
Interesting, is that classloader magic of breaking the delegation chain considered a hack or is it something legitimate?
Curious because this sounds like you can do what docker does with the OS but with Clojure and the JVM.
polylith does it for something (not sure what) https://github.com/polyfy/polylith/blob/76936c752fb5b729c216b23d92c8a8d71cfdc92f/components/common/src/polylith/clj/core/common/class_loader.clj
it has been discussed a little bit in #tools-deps as functionality people might want for some parts of the new tools.build stuff, I wouldn't be surprised if that could be added as a library there
I wrote something like it for work, and I think @U04V70XH6 may whip it into shape for using tools.build to run tests for our monorepo's subprojects
I do know about a million years ago at some point when I asked rhickey on irc about running multiple clojure runtimes in a single process, he said something like "same as java with classloaders"
I think I'm happier with running tests in a subprocess, at least for now, but Polylith does use that approach for running tests directly in-process with independent dependencies for each "project" so it's definitely something to keep in our back pocket in case we change our minds on that.
Boot's pods were very nice but they added caching/"pod pooling" behind the scenes and that was very buggy (partly because they would asynchronously clean pods up for reuse) and those bugs were a big reason we moved from Boot to the CLI/`deps.edn`.
It would be great if t.d.a provided "something" to set up isolated Clojure instances out of the box but I don't think that will happen any time soon (if at all).
it does seem like the kind of complicated feature that could be added as a library so core will avoid taking it on
@U04V70XH6 can you elaborate more on "isolated clojure instances" and what this would be good for? It sounds interesting. Like a kind of clojure container but there's only one JVM and garbage collector running.
and if the instances need to communicate then they're not really isolated, are they? What's the surface area of the connecting points?
@UJHMA11DJ Pretty much exactly what @U0NCTKEV8 was talking about: running code in multiple classloaders that are isolated. Boot used to do this with its "pods" but it was a bit fussy in places -- and part of it is because the initial instance has to set up the isolated instances and "communicate" with them to get the environment set up.
In Boot's case, you usually wanted to pass parameters into the process and get results out -- and that all has to be serialized in a way that avoids any classes that unique to each classloader (because a HashMap in one classloader is not going to look like a HashMap in another classloader -- because they are different classes... they just happen to have the same name, but their classloader is part of their identity).
clojurebot (the ircbot in a clojure irc channel) has some really horrendous code for doing this as part of it's sandboxed eval https://github.com/hiredman/clojurebot/blob/master/clojurebot-eval/src/clojurebot/sandbox.clj#L223
So I can use a different newer version of clojure in the sandbox without being forced to update the version of clojure the sandbox is built with
@U04V70XH6 That led to some interesting reading, thanks. What would have needed to be fixed for you to stay happy with Boot's pods?
@UJHMA11DJ Sorry, missed that Q. Two things: the fileset abstraction caused quite a performance overhead (and could also be problematic when doing dev work since source files often appeared to be in the "shadow" file locations instead of their actual locations); the async refresh of pod contexts was just plain buggy and caused us all sorts of problems in big, complex builds where pods got reused. I think if Boot's fileset abstraction was "good intentions" but not necessary and caused more problems than it solved. The whole pod pooling/reuse thing was definitely valuable -- pods were expensive to stand up on their own so cleaning them up and reusing them helped performance but it also caused weird async bugs.
Ok, well you basically end up needing to differentiate using the namespace name (say my-ns.dev.foo
) while you could have just used the classpath mechanism (`-A:dev`) for it and let the runtime compose.
I guess you would end up with what Java is doing (and conflicts) so I can see why this was chosen not to be the case.
trying to wrap my head around whether there's a way to write a macro to do something like
(with-uuid [uuid]
;; do something with uuid)
that would be equivalent to
(let [uuid (java.util.UUID/randomUUID)]
;;do something with uuid)
I tried something like
(defmacro with-uuid
[bindings & body]
`(let [uuid# (str (UUID/randomUUID))
bindings# (conj ~bindings uuid#)]
(let* bindings# ~@body)))
but using it complains about not being able to resolve the symbol uuid
If you want uuid
and for it to shadow any other uuid
at the scopes above, just remove the #
.
the uuid#
is the value of the uuid, rather than the symbol, defining the macro doesnt appear to fail, its running
(With-uuid [uuid] (println uuid))
that complains about not being able to resolve uuid
I'd recommend looking atone of the with-*
macros in clojure.core to understand how it's done.
Ah, I see what you wanted to do. Try this:
(defmacro with-uuid
[bindings & body]
(let [b (gensym "uuid")
bindings (conj bindings b)]
`(let [~b (str (UUID/randomUUID))]
(let* ~bindings ~@body))))
You must compute the bindings vector at macro expansion time, but your code tries to do that at run time.
@U0ERTS466 If you want the name from that vector to be the actual symbol used in the expansion, you want an anaphoric macro like this:
dev=> (defmacro with-uuid [[sym] & body] `(let [~sym (java.util.UUID/randomUUID)] ~@body))
#'dev/with-uuid
dev=> (with-uuid [x] (println x))
#uuid "30832455-a7b9-403d-98c3-f19d17177445"
nil
dev=> (with-uuid [y] (println y))
#uuid "58872d97-4944-4819-9854-5bcf4b4c6093"
nil
Just be aware that is "odd" syntax, so you'll find linters will likely complain about the symbol being undefined in the body of your macro: normally a binding has a symbol and an expression (making it easier to lint such macros "as if" they were clojure.core/with-open
for example (which has [sym expr]
as its binding form).
https://en.wikipedia.org/wiki/Anaphoric_macro -- background reading on what I meant there.
@U04V70XH6 thanks a lot!
What's going on here?
(= (conj clojure.lang.PersistentQueue/EMPTY 3)
(conj (clojure.lang.PersistentQueue/EMPTY) 3))
I'm confused why EMPTY
can be invoked. But calling ((conj clojure.lang.PersistentQueue/EMPTY 3))
gives the expected Queue cannot be cast to IFnthe first one is just syntax sugar for the second one, both are handled basically the same by the compiler
or rather they are both syntax sugar for the same .
form
there is ambiguity in clojure syntax around field accesses and no arg methods invokes
public final class Thing {
public static final int x = 4;
public static int x() { return 5; }
}
yes, methods are preferred
I guess its just a convenience thing, otherwise you'd be forced to use .- all the time like in ClojureScript
use .-
to force field access
fortunately, this overlap is pretty unusual in Java
well that's not an overlap here :)
❯ javap -p JrueHoliday.class
Compiled from "JrueHoliday.java"
final class JrueHoliday extends java.lang.Record {
private final int x;
JrueHoliday(int);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public int x();
}
yeah the field is private, method is publicside-note, metadata is cute but it seems quite highly likely to get lost in translation as it jumps across module boundaries clojure.core has some degree of care for metadata not to get easily lost, but still can, especially in face of non-clojure-core defns plus you'd have to spec data and metadata. it seems awkward to do some double work?
(def User [:and [:map
[:user/email string?]
[:user/password-hash string?]]
[:fn {:error/message "should have user metadata"}
(fn [o] (= (type o) ::user))]])
;; ----------------------------------------------------------------------------
(malli/=> create [:=>
[:cat [:map
[:email string?]
[:password-hash string?]]]
User])
(defn create
[{:keys [email password-hash]}]
^{:type ::user}
{:user/email email
:user/password-hash password-hash})
;; ----------------------------------------------------------------------------
(malli/=> email [:=> [:cat User] string?])
(def email :user/email)
;; ----------------------------------------------------------------------------
(malli/=> password-hash [:=> [:cat User] string?])
(def password-hash :user/password-hash)