Is there any way to mark a symbol as obsolete in Clojure?
I could use something like this from Lein which works well.
https://github.com/technomancy/leiningen/blob/1.x/src/leiningen/core.clj#L13
But the it only emits its warning when a function is called. I'd really like the compiler to pick this up at the time code is compiled, rather than when it is called.
Of course, I could just not define the symbol, which the compiler would then pick
up, but this robs me of the ability to provide any information, such as why, or when
the symbol has been deprecated.
All of this is for a DSL where deprecation and obsolescence of terms is going to happen at a reasonable rate.
There's already something in the comments about using macros, but as someone noted this precludes using the function as a HOF. You can get around this, although it may be that the cure isn't worse than the disease. For example, imagine your function is
(defn foo* [x y] (+ x y))
Then instead of writing
(defmacro foo [x y] (warn) `(foo* x y))
(foo 1 2)
(map foo [5 6] [7 8])
You can make the macroexpansion return a function, which of course can be used like any other:
(defmacro foo [] (warn) `foo*)
((foo) 1 2)
(map (foo) [5 6] [7 8])
Probably not worth the awkwardness, but does provide a way to complain whenever the deprecated feature is used while still keeping the possibility of HOF usage.
It really depends on how your DSL is designed. If you can design your DSL such that each form must be wrapped in a go or something similar call then you could write this go macro which does something like this:
macroexpand-all (from clojure.walk) to the passed form
Use postwalk (from clojure.walk) on the above returned form and check if any of the symbols encountered are deprecated by probably looking at some global list of deprecated symbols. You may need to do resolve on the symbols to get namespace qualified name of the symbol.
If any deprecated symbol found, the print message.
Return the input form as it is.
This go macro then can be used for other pre-processing of your DSL as this is the entry point for your DSL execution.
NOTE: You may need to come up with solution where user uses same name for his/her own symbol from one of the deprecated symbols, but again this may not be needed depends on your DSL design.
Related
I need a function that can tell whether a symbol is pointing to a special form or a macro.
I found the function? function in the clojure.test namespace, so could use that, but I'm hesitant to use it because it seems to be intended only for testing purposes. Is it okay to use it for normal code? If not, how can I accomplish my goal.
As noted the comments, fn? does not work because it only works on functions themselves, not the symbols that point to them.
If it does what you want, use it. It's in clojure.test because it wasn't expected to be useful for non-test code, but if it's the best function for your particular use case, there's no reason to hamper yourself just because of where the function is located. In other words, clojure.test is separate from clojure.core for organization, not because it should only ever be used for tests.
you can find all special form, :-)
(defn special-symbol?
[s]
(contains? (. clojure.lang.Compiler specials) s))
I have this macro:
(defmacro widget [msg-type value & app-key]
`(defrecord ~msg-type [~value]
Message
(~'process-message [msg# app#]
(let [state# (~#app-key app#)]
(dissoc
(->>
(merge state# msg#)
(assoc app# ~#app-key))
:errors)))))
Message is a protocol defined in a clojurescript dependency, with a process-message function.
When I try to use widget like so
(ns my.cljs.ns
(:require-macros [my.ns.macros :as macro])
(:require [petrol.core :refer [Message]]))
(macro/widget A-Record a-field :a-key)
I get this error:
Bad method signature in protocol implementation,
my.ns.macros/Message does not declare method called
process-message ...
How can I get Message to refer to petrol/Message instead of my.ns.macros/Message?
You need the power of the mystical ~' operator :)
I see you already invoked it for process-message, so perhaps you are already acquainted with why; but for the purposes of the answer, stuff in the backtick gets fully namespace qualified, where as evaluate quote puts the literal symbol in place.
(macroexpand-1 '(widget :a :b))
And the error message indicate that you need to ~'Message if you want to avoid it having the current ns attached to it.
However fully qualifying Message with the petrol namespace would be a good move IMO
petrol.core/Message
That way you don't need to rely on it being referred in the ns declaration. Note you don't need to ~' it either.
Also I would be wary of (~#app-key app#) because app-key are optional... you could get nothing passed in which would call whatever #app is, which doesn't sound like something you want to happen. Similarly passing more than one seems wierd to. Maybe it should be a required param?
In Clojure, if you call a function before its definition, e.g.
(foo (bar 'a))
(defn bar [] ...)
it is not compiled. One should add
(declare bar)
before (foo (bar 'a)). Why Clojure is designed as this? I mean, in most languages, except C/C++, such as Java, Python, PHP, Scala, Haskell or even other Lisps, especially in dynamic-type languages, function declaration is not needed, that is, function definition could be put either before or after a call. I feel it uncomfortable to use.
Clojure does a single-pass compilation (well I simplify, read the following links) :
https://news.ycombinator.com/item?id=2467359
https://news.ycombinator.com/item?id=2466912
So it seems logical that if you read the source only one time, from top to bottom you cannot have things like forward declaration and do it safely.
To quote Rich (first link) :
But, what should happen here, when the compiler has never before seen
bar?
`(defn foo [] (bar))`
or in CL:
`(defun foo () (bar))`
CL happily compiles it, and if bar is never defined, a runtime error will occur. Ok, but, what reified thing
(symbol) did it use for bar during compilation? The symbol it interned
when the form was read. So, what happens when you get the runtime
error and realize that bar is defined in another package you forgot to
import. You try to import other-package and, BAM!, another error -
conflict, other-package:bar conflicts with read-in-package:bar. Then
you go learn about uninterning.
In Clojure, the form doesn't compile,
you get a message, and no var is interned for bar. You require
other-namespace and continue.
I vastly prefer this experience, and so
made these tradeoffs. Many other benefits came about from using a
non-interning reader, and interning only on definition/declaration.
I'm not inclined to give them up, nor the benefits mentioned earlier,
in order to support circular reference.
According to spec, def should intern the var in the current ns (i.e. *ns*). However, the following code does not look anything like it:
(ns namespace-b)
(defn def_something []
(ns namespace-a)
(println *ns*) ;prints namespace-a as it should
(def something 1)
)
(def_something)
(println namespace-b/something) ; prints 1
(println namespace-a/something) ; throws
What am I missing?
Notes:
defn is used just for clarity. Defining and running anonymous function works just as well.
I know that using def inside function is probably not very idiomatic. However, this is just extracted essence of a bigger problem I ran into.
The parser already interns the var to the current namespace at compile time, although it won't be bound immediately:
(defn dd [] (def x 0))
x ;; => #<Unbound Unbound: #'user/x>
The relevant piece of code can be found here, with the second parameter to lookupVar triggering the aforementioned interning for non-existing vars here.
The parses then generates an expression that references the previously created var, so the expression logic never leaves the current namespace.
TL;DR: def is something that the compiler handles in a special kind of way.
The key thing to understand about def is that it is a macro. This means that it does not resolve the namespace or create the binding at runtime, but beforehand, while the code is being compiled.
If you call a function that calls def, that call to def was already resolved to use the namespace in which the function was defined. Similarly, if you call functions inside a function body, the functions to call are resolved at compile time within the namespace where that function was defined.
If you want to generally bind values to namespaces at runtime, you should use the function intern, which lets you explicitly set the namespace to mutate.
All this said, namespace mutation is just that, it's procedural and is not thread safe and does not have nice declarative semantics like other options Clojure makes available. I would strongly suggest finding a way to express your solution that does not involve unsafe runtime mutation.
Is there a way to use the reader with function values, e.g:
(read-string (pr-str +))
RuntimeException Unreadable form clojure.lang.Util.runtimeException
(Util.java:219)
?
As you might already know the output for (pr-str +) is not valid Clojure code that the reader can parse: "#<core$_PLUS_ clojure.core$_PLUS_#ff4805>". The output for function values when using the functions pr, prn, println and such, is intentionally wrapped around the #< reader macro that dispatches to the UnreadableReader, which throws the exception you are seeing.
For the example you provided you can use the print-dup function that works for basic serialization:
(defn string-fn [f]
(let [w (java.io.StringWriter.)]
(print-dup f w)
(str w)))
(let [plus (read-string (string-fn +))]
(plus 1 2))
The serialization done for the + function is actually generating the call to the class' constructor:
#=(clojure.core$_PLUS_. )
This only works of course if the class is already compiled in the Clojure environment where you are reading the string. If you serialized an anonymous function, saving it to a file and then reading it back in, when running a new REPL session, it will most likely not work since the class name for each anonymous function is different and depends on Clojure internals.
For arbitrary functions things get a lot more complicated. Sharing the source code might not even be enough, the function could rely on the usage of any number of other functions or vars that only exist in the source environment. If this is what you are thinking of doing, maybe considering other approaches to the problem you are trying to solve, will eliminate the need to serialize the value of arbitrary functions.
Hope it helps,
If you only need the name, can you just send the symbol with (name '+).
But generally speaking, it is a bad idea to use clojure read, if you want to read it back, as clojure's reader might execute some code in the process. Maybe have a look at the edn reader : clojure.edn/read-string
But maybe you just need to convert the string back to a symbol, in which case the (symbol name) function would be enough.