Discussion:
Prototype code for enhanced CIDER code completion for Java methods
'somewhat-functional-programmer' via Clojure
2018-10-12 00:22:23 UTC
Permalink
I'd like to share an idea and prototype code for better Java code completion in CIDER. While my main development environment is CIDER, the small modifications I made to support this idea were both to cider-nrepl and compliment -- which are both used by other Clojure tooling besides CIDER -- so maybe this is immediately more widely applicable.

In an effort to make it easier on the tooling, I'm using a slightly different syntax for calling Java methods. My inspiration is Kawa scheme, and the notation is very similar:

(String:charAt "my-string" 0) => \m
(Integer:parseInt "12") => 12
(possibly.fully.qualified.YourClassName:method this args)

For this syntax to be properly compiled of course it needs to be wrapped in a macro:

One form:
(jvm (String:charAt "my-string" 0))

Any number of forms:
(jvm
(lots of code)
(JavaClass:method ...)
(more code)
(AnotherJavaClass:method ...))

The jvm macro will transform any symbol it finds in the calling position of a list that follows the ClassName:method convention. I was thinking maybe of limiting it to just a particular namespace to absolutely prevent any name collisions with real clojure functions, something like:

(jvm/String:charAt "my-string" 0)

This will also work with the one-off test code I'm including here for folks to see what they think.

I actually like the syntax (though I wish I didn't have to wrap it in a jvm macro -- though if this actually idea was worth fully implementing, I'd imagine having new let or function macros so you don't even have to sprinkle "jvm" macros in code much at all).

There is one additional advantages to this style of Java interop besides the far better code completion:
- The jvm macro uses reflection to find the appropriate method at compile time, and as such, you get a compile error if the method cannot be found.
- This is a downside if you *want* reflection, but this of course doesn't preclude using the normal (.method obj args) notation.

You could even use this style for syntactic sugar for Java method handles:
- Though not implemented in my toy code here, you could also pass String:charAt as a clojure function -- assuming there were no overloads of the same arity.

So, I'm hoping you will try this out. Two things to copy/paste -- one is a boot command, the other is the 100-200 lines of clojure that implements a prototype of this.

This command pulls the necessary dependencies as well as starts up the rebel-readline repl (which is fantastic tool, and it also uses compliment for code completion):

# Run this somewhere where you can make an empty source directory,
# something fails in boot-tools-deps if you don't have one
# (much appreciate boot-tools-deps -- as cider-nrepl really needs to
# be a git dep for my purpose here since it's run through mranderson for its normal distro)
mkdir src && \
boot -d seancorfield/boot-tools-deps:0.4.6 \
-d compliment:0.3.6 -d cider/orchard:0.3.1 \
-d com.rpl/specter:1.1.1 -d com.taoensso/timbre:4.10.0 \
-d com.bhauman/rebel-readline:0.1.4 \
-d nrepl/nrepl:0.4.5 \
deps --config-data \
'{:deps {cider/cider-nrepl {:git/url "https://github.com/clojure-emacs/cider-nrepl.git" :sha "b2c0b920d762fdac2f8210805df2055af63f2eb1"}}}' \
call -f rebel-readline.main/-main

Paste the following code into the repl:

(require 'cider.nrepl.middleware.info)

(ns java-interop.core
(:require
[taoensso.timbre :as timbre
:refer [log trace debug info warn error fatal report
logf tracef debugf infof warnf errorf fatalf reportf
spy get-env]]
[clojure.reflect :as reflect]
[clojure.string :as s :refer [includes?]]
[com.rpl.specter :as sp]
[orchard.java :as java]))

(defn specific-class-member? [prefix]
;; NOTE: get a proper java class identifier here
(when-let [prefix (if (symbol? prefix)
(name prefix)
prefix)]
(and
(not (.startsWith prefix ":"))
(not (includes? prefix "::"))
(includes? prefix ":"))))

(def select-j-path
(sp/recursive-path
[] p
(sp/cond-path
#(and (seq? %) (specific-class-member? (first %)))
[(sp/continue-then-stay sp/ALL-WITH-META p)]
map? (sp/multi-path
[sp/MAP-KEYS p]
[sp/MAP-VALS p])
vector? [sp/ALL-WITH-META p]
seq? [sp/ALL-WITH-META p])))

(defmacro j [[s & [obj & args]]]
;; FIXME: Add better error checking later
;; FIXME: Java fields can have the same name as a method
(let [[clazz-str method-or-field & too-many-semis] (.split (name s) ":")
method-or-field-sym (symbol method-or-field)
clazz-sym (symbol clazz-str)]
(if-let [{:keys [flags return-type]} (first
(filter
#(= (:name %) method-or-field-sym)
(:members
(reflect/reflect
(ns-resolve *ns* clazz-sym)
:ancestors true))))]
(cond
(contains? flags :static) (concat
`(. ~clazz-sym ~method-or-field-sym)
(if obj
`(~obj))
args)
:else
(concat

`(. ~(if (symbol? obj)
(with-meta
obj
{:tag clazz-sym})
obj)
~(symbol method-or-field))
args))
(throw (ex-info "Method or field does not exist in class."
{:method method-or-field-sym
:class clazz-sym})))))

(defmacro jvm [& body]
(concat
`(do)
(map
#(sp/transform
select-j-path
(fn [form]
`(j ~form))
%)
body)))

;; for compliment code complete
(in-ns 'compliment.sources.class-members)
(require 'java-interop.core)

(defn members-candidates
"Returns a list of Java non-static fields and methods candidates."
[prefix ns context]
(cond
(class-member-symbol? prefix)
(let [prefix (subs prefix 1)
inparts? (re-find #"[A-Z]" prefix)
klass (try-get-object-class ns context)]
(for [[member-name members] (get-all-members ns)
:when (if inparts?
(camel-case-matches? prefix member-name)
(.startsWith ^String member-name prefix))
:when
(or (not klass)
(some #(= klass (.getDeclaringClass ^Member %)) members))]
{:candidate (str "." member-name)
:type (if (instance? Method (first members))
:method :field)}))

(java-interop.core/specific-class-member? prefix)
(let [sym (symbol prefix)
[clazz-str member-str & too-many-semis] (.split (name sym) #_prefix ":")]
(when (not too-many-semis)
(when-let [clazz
(resolve-class ns (symbol clazz-str))]
(->>
(clojure.reflect/reflect clazz :ancestors true)
(:members)
(filter #(and
;; public
(contains? (:flags %) :public)
;; but not a constructor
(not (and (not (:return-type %)) (:parameter-types %)))
;; and of course, the name must match
(or
(clojure.string/blank? member-str)
(.startsWith (str (:name %)) member-str))))
(map
(fn [{:keys [name type return-type]}]
{:candidate (str (when-let [n (namespace sym)]
(str (namespace sym) "/")) clazz-str ":" name)
:type (if return-type
:method
:field)}))))))))

;; for eldoc support in cider
(in-ns 'cider.nrepl.middleware.info)
(require 'orchard.info)

(defn java-special-sym [ns sym]
(let [sym-str (name sym)]
(if (clojure.string/includes? sym-str ":")
(when-let [[class member & too-many-semis] (.split sym-str ":")]
(if (and class
member
(not too-many-semis))
(when-let [resolved-clazz-sym
(some->>
(symbol class)
^Class (compliment.utils/resolve-class ns)
(.getName)
(symbol))]
[resolved-clazz-sym
(symbol member)]))))))

(defn info [{:keys [ns symbol class member] :as msg}]
(let [[ns symbol class member] (map orchard.misc/as-sym [ns symbol class member])]
(if-let [cljs-env (cider.nrepl.middleware.util.cljs/grab-cljs-env msg)]
(info-cljs cljs-env symbol ns)
(let [var-info (cond (and ns symbol) (or
(orchard.info/info ns symbol)
(when-let [[clazz member]
(java-special-sym ns symbol)]
(orchard.info/info-java clazz member)))
(and class member) (orchard.info/info-java class member)
:else (throw (Exception.
"Either \"symbol\", or (\"class\", \"member\") must be supplied")))
;; we have to use the resolved (real) namespace and name here
see-also (orchard.info/see-also (:ns var-info) (:name var-info))]
(if (seq see-also)
(merge {:see-also see-also} var-info)
var-info)))))

;; cider blows up if we don't have a project.clj file for it to read the version
;; string from

(ns cider.nrepl.version
;; We require print-method here because `cider.nrepl.version`
;; namespace is used by every connection.
(:require [cider.nrepl.print-method]
[clojure.java.io :as io]))

#_(def version-string
"The current version for cider-nrepl as a string."
(-> (or (io/resource "cider/cider-nrepl/project.clj")
"project.clj")
slurp
read-string
(nth 2)))

(def version-string "0.19.0-SNAPSHOT")

(def version
"Current version of CIDER nREPL as a map.
Map of :major, :minor, :incremental, :qualifier,
and :version-string."
(assoc (->> version-string
(re-find #"(\d+)\.(\d+)\.(\d+)-?(.*)")
rest
(map #(try (Integer/parseInt %) (catch Exception e nil)))
(zipmap [:major :minor :incremental :qualifier]))
:version-string version-string))

(defn cider-version-reply
"Returns CIDER-nREPL's version as a map which contains `:major`,
`:minor`, `:incremental`, and `:qualifier` keys, just as
`*clojure-version*` does."
[msg]
{:cider-version version})

(in-ns 'boot.user)
(require 'nrepl.server)

(defn nrepl-handler []
(require 'cider.nrepl)
(ns-resolve 'cider.nrepl 'cider-nrepl-handler))

(nrepl.server/start-server :port 7888 :handler (nrepl-handler))

(require '[java-interop.core :refer [jvm]])

;; NOTE: Code completion works in rebel-readline,
;; but it limits how many completions are shown at once
;; Try CIDER (you have an nrepl instance running now localhost:7888)
;; Eldoc also works in cider
;;
;; example
(jvm
[(Integer:parseInt "12") (String:charAt "test-string" 0)])

You should now have an nrepl server running on localhost:7888 which you can connect to from CIDER. You can try the completion (though not documentation) right in rebel-readline's repl.

So give it a try.... you'll notice static fields aren't handled perfectly (easy fix, but again I really am looking for feedback on the concept, and am wondering who would use it etc).

Right now you can access a static field like a method call:
(jvm (Integer:MAX_VALUE))

I think the code completion + eldoc in CIDER is a productivity boost for sure.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Didier
2018-10-15 16:59:56 UTC
Permalink
Could you find a way that doesn't require a syntax change?

Maybe a meta hint?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'Tatu Tarvainen' via Clojure
2018-10-16 15:47:02 UTC
Permalink
Nice. I like the syntax, I'll try it out.

But it seems unlikely to me that the interop forms would be changed in core
at this late stage.

As metadata tags can be added to symbols, could we write (^String .charAt
"test-string" 0)
It doesn't look as nice as your proposed syntax, but is possible without
modifications.

perjantai 12. lokakuuta 2018 3.22.37 UTC+3 somewhat-functional-programmer
Post by 'somewhat-functional-programmer' via Clojure
I'd like to share an idea and prototype code for better Java code
completion in CIDER. While my main development environment is CIDER, the
small modifications I made to support this idea were both to cider-nrepl
and compliment -- which are both used by other Clojure tooling besides
CIDER -- so maybe this is immediately more widely applicable.
In an effort to make it easier on the tooling, I'm using a slightly
different syntax for calling Java methods. My inspiration is Kawa scheme,
(String:charAt "my-string" 0) => \m
(Integer:parseInt "12") => 12
(possibly.fully.qualified.YourClassName:method this args)
(jvm (String:charAt "my-string" 0))
(jvm
(lots of code)
(JavaClass:method ...)
(more code)
(AnotherJavaClass:method ...))
The jvm macro will transform any symbol it finds in the calling position
of a list that follows the ClassName:method convention. I was thinking
maybe of limiting it to just a particular namespace to absolutely prevent
(jvm/String:charAt "my-string" 0)
This will also work with the one-off test code I'm including here for
folks to see what they think.
I actually like the syntax (though I wish I didn't have to wrap it in a
jvm macro -- though if this actually idea was worth fully implementing, I'd
imagine having new let or function macros so you don't even have to
sprinkle "jvm" macros in code much at all).
There is one additional advantages to this style of Java interop besides
- The jvm macro uses reflection to find the appropriate method at
compile time, and as such, you get a compile error if the method cannot be
found.
- This is a downside if you *want* reflection, but this of course
doesn't preclude using the normal (.method obj args) notation.
- Though not implemented in my toy code here, you could also pass
String:charAt as a clojure function -- assuming there were no overloads of
the same arity.
So, I'm hoping you will try this out. Two things to copy/paste -- one is
a boot command, the other is the 100-200 lines of clojure that implements a
prototype of this.
This command pulls the necessary dependencies as well as starts up the
rebel-readline repl (which is fantastic tool, and it also uses compliment
# Run this somewhere where you can make an empty source directory,
# something fails in boot-tools-deps if you don't have one
# (much appreciate boot-tools-deps -- as cider-nrepl really needs to
# be a git dep for my purpose here since it's run through mranderson
for its normal distro)
mkdir src && \
boot -d seancorfield/boot-tools-deps:0.4.6 \
-d compliment:0.3.6 -d cider/orchard:0.3.1 \
-d com.rpl/specter:1.1.1 -d com.taoensso/timbre:4.10.0 \
-d com.bhauman/rebel-readline:0.1.4 \
-d nrepl/nrepl:0.4.5 \
deps --config-data \
'{:deps {cider/cider-nrepl {:git/url "
https://github.com/clojure-emacs/cider-nrepl.git" :sha
"b2c0b920d762fdac2f8210805df2055af63f2eb1"}}}' \
call -f rebel-readline.main/-main
(require 'cider.nrepl.middleware.info)
(ns java-interop.core
(:require
[taoensso.timbre :as timbre
:refer [log trace debug info warn error fatal report
logf tracef debugf infof warnf errorf fatalf reportf
spy get-env]]
[clojure.reflect :as reflect]
[clojure.string :as s :refer [includes?]]
[com.rpl.specter :as sp]
[orchard.java :as java]))
(defn specific-class-member? [prefix]
;; NOTE: get a proper java class identifier here
(when-let [prefix (if (symbol? prefix)
(name prefix)
prefix)]
(and
(not (.startsWith prefix ":"))
(not (includes? prefix "::"))
(includes? prefix ":"))))
(def select-j-path
(sp/recursive-path
[] p
(sp/cond-path
#(and (seq? %) (specific-class-member? (first %)))
[(sp/continue-then-stay sp/ALL-WITH-META p)]
map? (sp/multi-path
[sp/MAP-KEYS p]
[sp/MAP-VALS p])
vector? [sp/ALL-WITH-META p]
seq? [sp/ALL-WITH-META p])))
(defmacro j [[s & [obj & args]]]
;; FIXME: Add better error checking later
;; FIXME: Java fields can have the same name as a method
(let [[clazz-str method-or-field & too-many-semis] (.split (name s) ":")
method-or-field-sym (symbol method-or-field)
clazz-sym (symbol clazz-str)]
(if-let [{:keys [flags return-type]} (first
(filter
#(= (:name %)
method-or-field-sym)
(:members
(reflect/reflect
(ns-resolve *ns* clazz-sym)
:ancestors true))))]
(cond
(contains? flags :static) (concat
`(. ~clazz-sym ~method-or-field-sym)
(if obj
`(~obj))
args)
:else
(concat
`(. ~(if (symbol? obj)
(with-meta
obj
{:tag clazz-sym})
obj)
~(symbol method-or-field))
args))
(throw (ex-info "Method or field does not exist in class."
{:method method-or-field-sym
:class clazz-sym})))))
(defmacro jvm [& body]
(concat
`(do)
(map
#(sp/transform
select-j-path
(fn [form]
`(j ~form))
%)
body)))
;; for compliment code complete
(in-ns 'compliment.sources.class-members)
(require 'java-interop.core)
(defn members-candidates
"Returns a list of Java non-static fields and methods candidates."
[prefix ns context]
(cond
(class-member-symbol? prefix)
(let [prefix (subs prefix 1)
inparts? (re-find #"[A-Z]" prefix)
klass (try-get-object-class ns context)]
(for [[member-name members] (get-all-members ns)
:when (if inparts?
(camel-case-matches? prefix member-name)
(.startsWith ^String member-name prefix))
:when
(or (not klass)
(some #(= klass (.getDeclaringClass ^Member %)) members))]
{:candidate (str "." member-name)
:type (if (instance? Method (first members))
:method :field)}))
(java-interop.core/specific-class-member? prefix)
(let [sym (symbol prefix)
[clazz-str member-str & too-many-semis] (.split (name sym) #_prefix ":")]
(when (not too-many-semis)
(when-let [clazz
(resolve-class ns (symbol clazz-str))]
(->>
(clojure.reflect/reflect clazz :ancestors true)
(:members)
(filter #(and
;; public
(contains? (:flags %) :public)
;; but not a constructor
(not (and (not (:return-type %)) (:parameter-types %)))
;; and of course, the name must match
(or
(clojure.string/blank? member-str)
(.startsWith (str (:name %)) member-str))))
(map
(fn [{:keys [name type return-type]}]
{:candidate (str (when-let [n (namespace sym)]
(str (namespace sym) "/")) clazz-str ":" name)
:type (if return-type
:method
:field)}))))))))
;; for eldoc support in cider
(in-ns 'cider.nrepl.middleware.info)
(require 'orchard.info)
(defn java-special-sym [ns sym]
(let [sym-str (name sym)]
(if (clojure.string/includes? sym-str ":")
(when-let [[class member & too-many-semis] (.split sym-str ":")]
(if (and class
member
(not too-many-semis))
(when-let [resolved-clazz-sym
(some->>
(symbol class)
^Class (compliment.utils/resolve-class ns)
(.getName)
(symbol))]
[resolved-clazz-sym
(symbol member)]))))))
(defn info [{:keys [ns symbol class member] :as msg}]
(let [[ns symbol class member] (map orchard.misc/as-sym [ns symbol class member])]
(if-let [cljs-env (cider.nrepl.middleware.util.cljs/grab-cljs-env msg)]
(info-cljs cljs-env symbol ns)
(let [var-info (cond (and ns symbol) (or
(orchard.info/info ns symbol)
(when-let [[clazz member]
(java-special-sym ns symbol)]
(orchard.info/info-java clazz member)))
(and class member) (orchard.info/info-java class member)
:else (throw (Exception.
"Either \"symbol\", or
(\"class\", \"member\") must be supplied")))
;; we have to use the resolved (real) namespace and name here
see-also (orchard.info/see-also (:ns var-info) (:name var-info))]
(if (seq see-also)
(merge {:see-also see-also} var-info)
var-info)))))
;; cider blows up if we don't have a project.clj file for it to read the version
;; string from
(ns cider.nrepl.version
;; We require print-method here because `cider.nrepl.version`
;; namespace is used by every connection.
(:require [cider.nrepl.print-method]
[clojure.java.io :as io]))
#_(def version-string
"The current version for cider-nrepl as a string."
(-> (or (io/resource "cider/cider-nrepl/project.clj")
"project.clj")
slurp
read-string
(nth 2)))
(def version-string "0.19.0-SNAPSHOT")
(def version
"Current version of CIDER nREPL as a map.
Map of :major, :minor, :incremental, :qualifier,
and :version-string."
(assoc (->> version-string
(re-find #"(\d+)\.(\d+)\.(\d+)-?(.*)")
rest
(map #(try (Integer/parseInt %) (catch Exception e nil)))
(zipmap [:major :minor :incremental :qualifier]))
:version-string version-string))
(defn cider-version-reply
"Returns CIDER-nREPL's version as a map which contains `:major`,
`:minor`, `:incremental`, and `:qualifier` keys, just as
`*clojure-version*` does."
[msg]
{:cider-version version})
(in-ns 'boot.user)
(require 'nrepl.server)
(defn nrepl-handler []
(require 'cider.nrepl)
(ns-resolve 'cider.nrepl 'cider-nrepl-handler))
(nrepl.server/start-server :port 7888 :handler (nrepl-handler))
(require '[java-interop.core :refer [jvm]])
;; NOTE: Code completion works in rebel-readline,
;; but it limits how many completions are shown at once
;; Try CIDER (you have an nrepl instance running now localhost:7888)
;; Eldoc also works in cider
;;
;; example
(jvm
[(Integer:parseInt "12") (String:charAt "test-string" 0)])
You should now have an nrepl server running on localhost:7888 which you
can connect to from CIDER. You can try the completion (though not
documentation) right in rebel-readline's repl.
So give it a try.... you'll notice static fields aren't handled perfectly
(easy fix, but again I really am looking for feedback on the concept, and
am wondering who would use it etc).
(jvm (Integer:MAX_VALUE))
I think the code completion + eldoc in CIDER is a productivity boost for sure.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Timothy Baldridge
2018-10-16 16:23:19 UTC
Permalink
I don't understand why this is needed. Why can't cider code complete on the
normal method format? What's the problem this code is trying to solve?

Timothy
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-16 19:25:46 UTC
Permalink
I apologize for diving into a solution in my first email -- let me give a little more of the background as to what I am trying to accomplish.

I'm proposing an additional syntax for Java interop for the purpose of allowing support for more precise code-completion of Java fields and methods in Clojure tooling. The new syntax proposal is why I'm emailing this list rather than directly going to the project owners of the projects I've modified to support this. If there is high resistance to any additional syntax for Java interop, there's not much reason to try to convince the project owners of cider-nrepl and compliment to support something just for my personal use :-).

Note also that when I say "propose additional syntax" I do not mean "please add this to clojure lang" -- I've implemented this as a couple of macros which should be the way the language normally gets extended. I just want feedback and am trying to gauge interest, because if no one wants this then it's not worth any of the additional effort to "publish" it -- I'll just manage it as a utility library for myself.

So when I say more precise code completion for Java fields and methods, I mean this:
- I want my development environment or REPL to know the type of the Java object I'm operating on so it can limit the completion list to fields or methods that are valid for that particular type.
- For example, when typing:
(.get
I'd somehow like my environment to know that I want code completion for a Java object of type "this.is.AType"
and as such, I would only see methods or fields starting with ".get" for that type
What happens for me now, is I see some Java methods and fields starting with ".get" but on a number of Java objects (and not a complete list at that).
(I believe the tooling looks through symbols in your namespace, finds Java symbols, and starts pulling methods and fields from any of them). Using only the text "(.get" the environment cannot know what I'm trying to do.

Now the tooling could do static code analysis, it could look at the surrounding top-level form, and say, what are the locals, are they type-hinted, and only show me Java methods and fields from locals that have type hints. Even this approach though, which is complex and error prone to implement, still doesn't limit my list to just methods and fields of "this.is.AType" unless I only have one type-hinted local symbol.

So... if we had a syntax that looked like:
(Type:method obj? args)
My tooling becomes drastically simpler. Now my tooling is fed:
"Type:method"
And code completion simply becomes:
- Look up Type in my active namespace symbols (to find out if it's a Java type I've imported) (or it could simply be fully qualified, both are supported)
- Use reflection to find a list of candidate fields and methods
No static analysis required, simple to implement.

It's so simple to implement that I hastily pasted in my proof-of-concept/prototype code in the first message -- which hopefully is easy for folks to just try. I hope that people will try it and think: this would be great if I were writing a lot of Java interop code -- or better yet, give suggestions on how to improve it.

So try this by:
1) Install boot (or update it via "boot -u")
2) Make a new temp directory and navigate to it
3) Run the first command from my prior email in bash
4) Paste in the code snippet to the resulting rebel-readline repl
5) Try the limited completion right in rebel-readline
6) Connect using your cider-nrepl based tooling (CIDER, inf-clojure, vim-fireplace, others?), and try it from one of those tools. I've only tested with CIDER -- in my CIDER I get eldoc, help at symbol at point (I wish this displayed javadoc inline), and better code completion.
7) Feedback! :-)

I also realize that this stuff is complex enough that there might be some set of CIDER settings that solve this problem that I just simply don't know about or didn't find. Love to hear about how others get better Java interop code completion. Some of my clojure projects involve lots of Java interop and this would have been sooooo nice to have while writing those projects (in my opinion).

I think there are other advantages to this style syntax (besides supporting code completion):
- Right now I have the jvm macro throw an error if it can't find the java symbol -- so you would get a compile time error rather than just a reflection warning about a non-existent java method.
- In addition to making easier to *write* Java interop, I think it makes it easier to *read* Java interop
I immediately know the Java type while reading the code, no need to hunt down the type annotations elsewhere. I've been writing Java since Java 1.1 and I still like the reminder of what class a method is from.

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
I don't understand why this is needed. Why can't cider code complete on the normal method format? What's the problem this code is trying to solve?
Timothy
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Timothy Baldridge
2018-10-16 19:46:21 UTC
Permalink
As you say, this is a limitation in the code completer. In Cursive this
problem doesn't exist to this extent, when I type `(.get` the completer
responds with a list of methods and classes that could be completed to that
method, starting with classes in the namespace I'm currently editing.

Yes, this takes static analysis, or perhaps some indexing and
introspection. But changing the syntax is never really going to gain
traction here, as manually typing the name of every method just so my
editor can produce a list seems like me conforming to my tool rather than
my tool learning to work with me.

The imported classes in the current namespace are stored in the mappings
attribute. It seems like an editor should be able to install hooks into
that and index the methods on the classes to provide this feedback.
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Namespace.java#L123


On Tue, Oct 16, 2018 at 1:26 PM 'somewhat-functional-programmer' via
Post by 'somewhat-functional-programmer' via Clojure
I apologize for diving into a solution in my first email -- let me give a
little more of the background as to what I am trying to accomplish.
I'm proposing an additional syntax for Java interop for the purpose of
allowing support for more precise code-completion of Java fields and
methods in Clojure tooling. The new syntax proposal is why I'm emailing
this list rather than directly going to the project owners of the projects
I've modified to support this. If there is high resistance to any
additional syntax for Java interop, there's not much reason to try to
convince the project owners of cider-nrepl and compliment to support
something just for my personal use :-).
Note also that when I say "propose additional syntax" I do not mean
"please add this to clojure lang" -- I've implemented this as a couple of
macros which should be the way the language normally gets extended. I just
want feedback and am trying to gauge interest, because if no one wants this
then it's not worth any of the additional effort to "publish" it -- I'll
just manage it as a utility library for myself.
- I want my development environment or REPL to know the type of the Java
object I'm operating on so it can limit the completion list to fields or
methods that are valid for that particular type.
(.get
I'd somehow like my environment to know that I want code completion
for a Java object of type "this.is.AType"
and as such, I would only see methods or fields starting with ".get" for that type
What happens for me now, is I see some Java methods and fields
starting with ".get" but on a number of Java objects (and not a complete
list at that).
(I believe the tooling looks through symbols in your namespace,
finds Java symbols, and starts pulling methods and fields from any of
them). Using only the text "(.get" the environment cannot know what I'm
trying to do.
Now the tooling could do static code analysis, it could look at the
surrounding top-level form, and say, what are the locals, are they
type-hinted, and only show me Java methods and fields from locals that have
type hints. Even this approach though, which is complex and error prone to
implement, still doesn't limit my list to just methods and fields of
"this.is.AType" unless I only have one type-hinted local symbol.
(Type:method obj? args)
"Type:method"
- Look up Type in my active namespace symbols (to find out if it's a
Java type I've imported) (or it could simply be fully qualified, both are
supported)
- Use reflection to find a list of candidate fields and methods
No static analysis required, simple to implement.
It's so simple to implement that I hastily pasted in my
proof-of-concept/prototype code in the first message -- which hopefully is
easy for folks to just try. I hope that people will try it and think: this
would be great if I were writing a lot of Java interop code -- or better
yet, give suggestions on how to improve it.
1) Install boot (or update it via "boot -u")
2) Make a new temp directory and navigate to it
3) Run the first command from my prior email in bash
4) Paste in the code snippet to the resulting rebel-readline repl
5) Try the limited completion right in rebel-readline
6) Connect using your cider-nrepl based tooling (CIDER, inf-clojure,
vim-fireplace, others?), and try it from one of those tools. I've only
tested with CIDER -- in my CIDER I get eldoc, help at symbol at point (I
wish this displayed javadoc inline), and better code completion.
7) Feedback! :-)
I also realize that this stuff is complex enough that there might be some
set of CIDER settings that solve this problem that I just simply don't know
about or didn't find. Love to hear about how others get better Java
interop code completion. Some of my clojure projects involve lots of Java
interop and this would have been sooooo nice to have while writing those
projects (in my opinion).
I think there are other advantages to this style syntax (besides
- Right now I have the jvm macro throw an error if it can't find the
java symbol -- so you would get a compile time error rather than just a
reflection warning about a non-existent java method.
- In addition to making easier to *write* Java interop, I think it makes
it easier to *read* Java interop
I immediately know the Java type while reading the code, no need to
hunt down the type annotations elsewhere. I've been writing Java since
Java 1.1 and I still like the reminder of what class a method is from.
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Tuesday, October 16, 2018 4:23 PM, Timothy Baldridge <
I don't understand why this is needed. Why can't cider code complete on
the normal method format? What's the problem this code is trying to solve?
Timothy
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
“One of the main causes of the fall of the Roman Empire was that–lacking
zero–they had no way to indicate successful termination of their C
programs.”
(Robert Firth)
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-17 00:29:56 UTC
Permalink
Comments inline... I really appreciate you taking the time to look at this. I think I am still imprecise in my language -- I hope the comments below doesn't come across as too tedious :-)...

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
As you say, this is a limitation in the code completer. In Cursive this problem doesn't exist to this extent, when I type `(.get` the completer responds with a list of methods and classes that could be completed to that method, starting with classes in the namespace I'm currently editing.
I think we are saying the same thing here. I believe compliment (the library CIDER/other clojure tooling uses for code completion) does what we are describing (showing Java methods and fields from multiple Java types that are imported into the namespace currently being edited (or type hinted in locals/function definitions). My point is I want more -- I want the completion list to only include methods and fields from the type I as a developer know that I have.

Like a Java IDE:
MyType a = new MyType();
Now typing "a." yields just completions valid for MyType, and not 5 other types I've used nearby.
Yes, this takes static analysis, or perhaps some indexing and introspection. But changing the syntax is never really going to gain traction here, as manually typing the name of every method just so my editor can produce a list seems like me conforming to my tool rather than my tool learning to work with me.
Just to make sure I'm completely clear -- I'm *not* advocating a change to clojure lang -- only proposing a macro/library. The prototype macro I wrote simply transforms the (Type:method ...) syntax into the (. instance-expr member-symbol) that (.method ...) macroexpands into anyhow. This is not intended to replace (.method obj args) notation.

I'm not sure what you mean by "manually typing the name of every method just so my editor can produce a list".

The way this works is, you type "(MyType:" and get presented with a list of completions that are *only* applicable to MyType -- there's no manually typing lists of methods and fields anywhere. And, the way this works for me in CIDER, I type "(My<TAB>" and I get "(MyType", then I add a ":", and now I get completions just for MyType -- it also shows me the Java signature of the methods as I highlight a potential completion. There's no manually seeding the list anywhere...
The imported classes in the current namespace are stored in the mappings attribute. It seems like an editor should be able to install hooks into that and index the methods on the classes to provide this feedback. https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Namespace.java#L123
Yes I agree -- and I believe that's exactly what the existing tooling already does -- I just want more precision.

Some of my motivation has been working with monstrously large class libraries in Java. GIS libraries in Java are notoriously class/method heavy and after importing many of these classes into a namespace, completion that cannot narrow down to the exact type I'm using for the reasons we agree on -- simply is painful to work with.
Post by 'somewhat-functional-programmer' via Clojure
I apologize for diving into a solution in my first email -- let me give a little more of the background as to what I am trying to accomplish.
I'm proposing an additional syntax for Java interop for the purpose of allowing support for more precise code-completion of Java fields and methods in Clojure tooling. The new syntax proposal is why I'm emailing this list rather than directly going to the project owners of the projects I've modified to support this. If there is high resistance to any additional syntax for Java interop, there's not much reason to try to convince the project owners of cider-nrepl and compliment to support something just for my personal use :-).
Note also that when I say "propose additional syntax" I do not mean "please add this to clojure lang" -- I've implemented this as a couple of macros which should be the way the language normally gets extended. I just want feedback and am trying to gauge interest, because if no one wants this then it's not worth any of the additional effort to "publish" it -- I'll just manage it as a utility library for myself.
- I want my development environment or REPL to know the type of the Java object I'm operating on so it can limit the completion list to fields or methods that are valid for that particular type.
(.get
I'd somehow like my environment to know that I want code completion for a Java object of type "this.is.AType"
and as such, I would only see methods or fields starting with ".get" for that type
What happens for me now, is I see some Java methods and fields starting with ".get" but on a number of Java objects (and not a complete list at that).
(I believe the tooling looks through symbols in your namespace, finds Java symbols, and starts pulling methods and fields from any of them). Using only the text "(.get" the environment cannot know what I'm trying to do.
Now the tooling could do static code analysis, it could look at the surrounding top-level form, and say, what are the locals, are they type-hinted, and only show me Java methods and fields from locals that have type hints. Even this approach though, which is complex and error prone to implement, still doesn't limit my list to just methods and fields of "this.is.AType" unless I only have one type-hinted local symbol.
(Type:method obj? args)
"Type:method"
- Look up Type in my active namespace symbols (to find out if it's a Java type I've imported) (or it could simply be fully qualified, both are supported)
- Use reflection to find a list of candidate fields and methods
No static analysis required, simple to implement.
It's so simple to implement that I hastily pasted in my proof-of-concept/prototype code in the first message -- which hopefully is easy for folks to just try. I hope that people will try it and think: this would be great if I were writing a lot of Java interop code -- or better yet, give suggestions on how to improve it.
1) Install boot (or update it via "boot -u")
2) Make a new temp directory and navigate to it
3) Run the first command from my prior email in bash
4) Paste in the code snippet to the resulting rebel-readline repl
5) Try the limited completion right in rebel-readline
6) Connect using your cider-nrepl based tooling (CIDER, inf-clojure, vim-fireplace, others?), and try it from one of those tools. I've only tested with CIDER -- in my CIDER I get eldoc, help at symbol at point (I wish this displayed javadoc inline), and better code completion.
7) Feedback! :-)
I also realize that this stuff is complex enough that there might be some set of CIDER settings that solve this problem that I just simply don't know about or didn't find. Love to hear about how others get better Java interop code completion. Some of my clojure projects involve lots of Java interop and this would have been sooooo nice to have while writing those projects (in my opinion).
- Right now I have the jvm macro throw an error if it can't find the java symbol -- so you would get a compile time error rather than just a reflection warning about a non-existent java method.
- In addition to making easier to *write* Java interop, I think it makes it easier to *read* Java interop
I immediately know the Java type while reading the code, no need to hunt down the type annotations elsewhere. I've been writing Java since Java 1.1 and I still like the reminder of what class a method is from.
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
I don't understand why this is needed. Why can't cider code complete on the normal method format? What's the problem this code is trying to solve?
Timothy
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Aaron Cohen
2018-10-17 03:56:12 UTC
Permalink
This seems like it could be done using threading.

(-> "my-string"
(.ch <-- completion should give you good results here, for only
String methods


(-> (new MyType)
(. <-- completion should give you only methods of MyType


; One interesting case is the following:
(def foo "hello")

; foo's type isn't known, so would need to be hinted
(-> ^String foo
(.to <-- good completions again, because of the type hint

I think you won't be able to get all the way to your jvm macro, but likely
pretty close, and it's much more idiomatic...

The doto macro is also useful in a similar way, and often what you want
when using some of the more byzantine java libraries.

(All of the above works in Cursive, I'm not sure about how it works in
CIDER, but I assume it's equivalent).

--Aaron



On Tue, Oct 16, 2018 at 8:30 PM 'somewhat-functional-programmer' via
Post by 'somewhat-functional-programmer' via Clojure
Comments inline... I really appreciate you taking the time to look at
this. I think I am still imprecise in my language -- I hope the comments
below doesn't come across as too tedious :-)...
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Tuesday, October 16, 2018 7:46 PM, Timothy Baldridge <
As you say, this is a limitation in the code completer. In Cursive this
problem doesn't exist to this extent, when I type `(.get` the completer
responds with a list of methods and classes that could be completed to that
method, starting with classes in the namespace I'm currently editing.
I think we are saying the same thing here. I believe compliment (the
library CIDER/other clojure tooling uses for code completion) does what we
are describing (showing Java methods and fields from multiple Java types
that are imported into the namespace currently being edited (or type hinted
in locals/function definitions). My point is I want more -- I want the
completion list to only include methods and fields from the type I as a
developer know that I have.
MyType a = new MyType();
Now typing "a." yields just completions valid for MyType, and not 5 other
types I've used nearby.
Yes, this takes static analysis, or perhaps some indexing and
introspection. But changing the syntax is never really going to gain
traction here, as manually typing the name of every method just so my
editor can produce a list seems like me conforming to my tool rather than
my tool learning to work with me.
Just to make sure I'm completely clear -- I'm *not* advocating a change to
clojure lang -- only proposing a macro/library. The prototype macro I
wrote simply transforms the (Type:method ...) syntax into the (.
instance-expr member-symbol) that (.method ...) macroexpands into anyhow.
This is not intended to replace (.method obj args) notation.
I'm not sure what you mean by "manually typing the name of every method
just so my editor can produce a list".
The way this works is, you type "(MyType:" and get presented with a list
of completions that are *only* applicable to MyType -- there's no manually
typing lists of methods and fields anywhere. And, the way this works for
me in CIDER, I type "(My<TAB>" and I get "(MyType", then I add a ":", and
now I get completions just for MyType -- it also shows me the Java
signature of the methods as I highlight a potential completion. There's no
manually seeding the list anywhere...
The imported classes in the current namespace are stored in the mappings
attribute. It seems like an editor should be able to install hooks into
that and index the methods on the classes to provide this feedback.
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Namespace.java#L123
Yes I agree -- and I believe that's exactly what the existing tooling
already does -- I just want more precision.
Some of my motivation has been working with monstrously large class
libraries in Java. GIS libraries in Java are notoriously class/method
heavy and after importing many of these classes into a namespace,
completion that cannot narrow down to the exact type I'm using for the
reasons we agree on -- simply is painful to work with.
On Tue, Oct 16, 2018 at 1:26 PM 'somewhat-functional-programmer' via
Post by 'somewhat-functional-programmer' via Clojure
I apologize for diving into a solution in my first email -- let me give a
little more of the background as to what I am trying to accomplish.
I'm proposing an additional syntax for Java interop for the purpose of
allowing support for more precise code-completion of Java fields and
methods in Clojure tooling. The new syntax proposal is why I'm emailing
this list rather than directly going to the project owners of the projects
I've modified to support this. If there is high resistance to any
additional syntax for Java interop, there's not much reason to try to
convince the project owners of cider-nrepl and compliment to support
something just for my personal use :-).
Note also that when I say "propose additional syntax" I do not mean
"please add this to clojure lang" -- I've implemented this as a couple of
macros which should be the way the language normally gets extended. I just
want feedback and am trying to gauge interest, because if no one wants this
then it's not worth any of the additional effort to "publish" it -- I'll
just manage it as a utility library for myself.
- I want my development environment or REPL to know the type of the
Java object I'm operating on so it can limit the completion list to fields
or methods that are valid for that particular type.
(.get
I'd somehow like my environment to know that I want code completion
for a Java object of type "this.is.AType"
and as such, I would only see methods or fields starting with ".get" for that type
What happens for me now, is I see some Java methods and fields
starting with ".get" but on a number of Java objects (and not a complete
list at that).
(I believe the tooling looks through symbols in your namespace,
finds Java symbols, and starts pulling methods and fields from any of
them). Using only the text "(.get" the environment cannot know what I'm
trying to do.
Now the tooling could do static code analysis, it could look at the
surrounding top-level form, and say, what are the locals, are they
type-hinted, and only show me Java methods and fields from locals that have
type hints. Even this approach though, which is complex and error prone to
implement, still doesn't limit my list to just methods and fields of
"this.is.AType" unless I only have one type-hinted local symbol.
(Type:method obj? args)
"Type:method"
- Look up Type in my active namespace symbols (to find out if it's a
Java type I've imported) (or it could simply be fully qualified, both are
supported)
- Use reflection to find a list of candidate fields and methods
No static analysis required, simple to implement.
It's so simple to implement that I hastily pasted in my
proof-of-concept/prototype code in the first message -- which hopefully is
easy for folks to just try. I hope that people will try it and think: this
would be great if I were writing a lot of Java interop code -- or better
yet, give suggestions on how to improve it.
1) Install boot (or update it via "boot -u")
2) Make a new temp directory and navigate to it
3) Run the first command from my prior email in bash
4) Paste in the code snippet to the resulting rebel-readline repl
5) Try the limited completion right in rebel-readline
6) Connect using your cider-nrepl based tooling (CIDER, inf-clojure,
vim-fireplace, others?), and try it from one of those tools. I've only
tested with CIDER -- in my CIDER I get eldoc, help at symbol at point (I
wish this displayed javadoc inline), and better code completion.
7) Feedback! :-)
I also realize that this stuff is complex enough that there might be some
set of CIDER settings that solve this problem that I just simply don't know
about or didn't find. Love to hear about how others get better Java
interop code completion. Some of my clojure projects involve lots of Java
interop and this would have been sooooo nice to have while writing those
projects (in my opinion).
I think there are other advantages to this style syntax (besides
- Right now I have the jvm macro throw an error if it can't find the
java symbol -- so you would get a compile time error rather than just a
reflection warning about a non-existent java method.
- In addition to making easier to *write* Java interop, I think it
makes it easier to *read* Java interop
I immediately know the Java type while reading the code, no need to
hunt down the type annotations elsewhere. I've been writing Java since
Java 1.1 and I still like the reminder of what class a method is from.
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Tuesday, October 16, 2018 4:23 PM, Timothy Baldridge <
I don't understand why this is needed. Why can't cider code complete on
the normal method format? What's the problem this code is trying to solve?
Timothy
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
“One of the main causes of the fall of the Roman Empire was that–lacking
zero–they had no way to indicate successful termination of their C
programs.”
(Robert Firth)
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-17 11:19:41 UTC
Permalink
I appreciate your thoughtful response -- I wish some of the other tooling could do this level of analysis but I can only imagine the time it took Colin to implement :-). Like I mentioned in my response to him -- I'm going to have to seriously consider leaving the cult of emacs not only for Java but maybe Clojure too :-).

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
Post by Aaron Cohen
This seems like it could be done using threading.
(-> "my-string"
(.ch <-- completion should give you good results here, for only String methods
(-> (new MyType)
(. <-- completion should give you only methods of MyType
(def foo "hello")
; foo's type isn't known, so would need to be hinted
(-> ^String foo
(.to <-- good completions again, because of the type hint
I think you won't be able to get all the way to your jvm macro, but likely pretty close, and it's much more idiomatic...
The doto macro is also useful in a similar way, and often what you want when using some of the more byzantine java libraries.
(All of the above works in Cursive, I'm not sure about how it works in CIDER, but I assume it's equivalent).
--Aaron
Post by 'somewhat-functional-programmer' via Clojure
Comments inline... I really appreciate you taking the time to look at this. I think I am still imprecise in my language -- I hope the comments below doesn't come across as too tedious :-)...
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
As you say, this is a limitation in the code completer. In Cursive this problem doesn't exist to this extent, when I type `(.get` the completer responds with a list of methods and classes that could be completed to that method, starting with classes in the namespace I'm currently editing.
I think we are saying the same thing here. I believe compliment (the library CIDER/other clojure tooling uses for code completion) does what we are describing (showing Java methods and fields from multiple Java types that are imported into the namespace currently being edited (or type hinted in locals/function definitions). My point is I want more -- I want the completion list to only include methods and fields from the type I as a developer know that I have.
MyType a = new MyType();
Now typing "a." yields just completions valid for MyType, and not 5 other types I've used nearby.
Yes, this takes static analysis, or perhaps some indexing and introspection. But changing the syntax is never really going to gain traction here, as manually typing the name of every method just so my editor can produce a list seems like me conforming to my tool rather than my tool learning to work with me.
Just to make sure I'm completely clear -- I'm *not* advocating a change to clojure lang -- only proposing a macro/library. The prototype macro I wrote simply transforms the (Type:method ...) syntax into the (. instance-expr member-symbol) that (.method ...) macroexpands into anyhow. This is not intended to replace (.method obj args) notation.
I'm not sure what you mean by "manually typing the name of every method just so my editor can produce a list".
The way this works is, you type "(MyType:" and get presented with a list of completions that are *only* applicable to MyType -- there's no manually typing lists of methods and fields anywhere. And, the way this works for me in CIDER, I type "(My<TAB>" and I get "(MyType", then I add a ":", and now I get completions just for MyType -- it also shows me the Java signature of the methods as I highlight a potential completion. There's no manually seeding the list anywhere...
The imported classes in the current namespace are stored in the mappings attribute. It seems like an editor should be able to install hooks into that and index the methods on the classes to provide this feedback. https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Namespace.java#L123
Yes I agree -- and I believe that's exactly what the existing tooling already does -- I just want more precision.
Some of my motivation has been working with monstrously large class libraries in Java. GIS libraries in Java are notoriously class/method heavy and after importing many of these classes into a namespace, completion that cannot narrow down to the exact type I'm using for the reasons we agree on -- simply is painful to work with.
Post by 'somewhat-functional-programmer' via Clojure
I apologize for diving into a solution in my first email -- let me give a little more of the background as to what I am trying to accomplish.
I'm proposing an additional syntax for Java interop for the purpose of allowing support for more precise code-completion of Java fields and methods in Clojure tooling. The new syntax proposal is why I'm emailing this list rather than directly going to the project owners of the projects I've modified to support this. If there is high resistance to any additional syntax for Java interop, there's not much reason to try to convince the project owners of cider-nrepl and compliment to support something just for my personal use :-).
Note also that when I say "propose additional syntax" I do not mean "please add this to clojure lang" -- I've implemented this as a couple of macros which should be the way the language normally gets extended. I just want feedback and am trying to gauge interest, because if no one wants this then it's not worth any of the additional effort to "publish" it -- I'll just manage it as a utility library for myself.
- I want my development environment or REPL to know the type of the Java object I'm operating on so it can limit the completion list to fields or methods that are valid for that particular type.
(.get
I'd somehow like my environment to know that I want code completion for a Java object of type "this.is.AType"
and as such, I would only see methods or fields starting with ".get" for that type
What happens for me now, is I see some Java methods and fields starting with ".get" but on a number of Java objects (and not a complete list at that).
(I believe the tooling looks through symbols in your namespace, finds Java symbols, and starts pulling methods and fields from any of them). Using only the text "(.get" the environment cannot know what I'm trying to do.
Now the tooling could do static code analysis, it could look at the surrounding top-level form, and say, what are the locals, are they type-hinted, and only show me Java methods and fields from locals that have type hints. Even this approach though, which is complex and error prone to implement, still doesn't limit my list to just methods and fields of "this.is.AType" unless I only have one type-hinted local symbol.
(Type:method obj? args)
"Type:method"
- Look up Type in my active namespace symbols (to find out if it's a Java type I've imported) (or it could simply be fully qualified, both are supported)
- Use reflection to find a list of candidate fields and methods
No static analysis required, simple to implement.
It's so simple to implement that I hastily pasted in my proof-of-concept/prototype code in the first message -- which hopefully is easy for folks to just try. I hope that people will try it and think: this would be great if I were writing a lot of Java interop code -- or better yet, give suggestions on how to improve it.
1) Install boot (or update it via "boot -u")
2) Make a new temp directory and navigate to it
3) Run the first command from my prior email in bash
4) Paste in the code snippet to the resulting rebel-readline repl
5) Try the limited completion right in rebel-readline
6) Connect using your cider-nrepl based tooling (CIDER, inf-clojure, vim-fireplace, others?), and try it from one of those tools. I've only tested with CIDER -- in my CIDER I get eldoc, help at symbol at point (I wish this displayed javadoc inline), and better code completion.
7) Feedback! :-)
I also realize that this stuff is complex enough that there might be some set of CIDER settings that solve this problem that I just simply don't know about or didn't find. Love to hear about how others get better Java interop code completion. Some of my clojure projects involve lots of Java interop and this would have been sooooo nice to have while writing those projects (in my opinion).
- Right now I have the jvm macro throw an error if it can't find the java symbol -- so you would get a compile time error rather than just a reflection warning about a non-existent java method.
- In addition to making easier to *write* Java interop, I think it makes it easier to *read* Java interop
I immediately know the Java type while reading the code, no need to hunt down the type annotations elsewhere. I've been writing Java since Java 1.1 and I still like the reminder of what class a method is from.
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
I don't understand why this is needed. Why can't cider code complete on the normal method format? What's the problem this code is trying to solve?
Timothy
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Alan Thompson
2018-10-17 15:16:34 UTC
Permalink
I love Cursive and use the IntelliJ Vim keybindings everyday. Apparently
they have alternate keybindings for those of you from the dark side.
Alan

On Wed, Oct 17, 2018 at 4:19 AM 'somewhat-functional-programmer' via
Post by 'somewhat-functional-programmer' via Clojure
I appreciate your thoughtful response -- I wish some of the other tooling
could do this level of analysis but I can only imagine the time it took
Colin to implement :-). Like I mentioned in my response to him -- I'm
going to have to seriously consider leaving the cult of emacs not only for
Java but maybe Clojure too :-).
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
This seems like it could be done using threading.
(-> "my-string"
(.ch <-- completion should give you good results here, for only String methods
(-> (new MyType)
(. <-- completion should give you only methods of MyType
(def foo "hello")
; foo's type isn't known, so would need to be hinted
(-> ^String foo
(.to <-- good completions again, because of the type hint
I think you won't be able to get all the way to your jvm macro, but likely
pretty close, and it's much more idiomatic...
The doto macro is also useful in a similar way, and often what you want
when using some of the more byzantine java libraries.
(All of the above works in Cursive, I'm not sure about how it works in
CIDER, but I assume it's equivalent).
--Aaron
On Tue, Oct 16, 2018 at 8:30 PM 'somewhat-functional-programmer' via
Post by 'somewhat-functional-programmer' via Clojure
Comments inline... I really appreciate you taking the time to look at
this. I think I am still imprecise in my language -- I hope the comments
below doesn't come across as too tedious :-)...
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Tuesday, October 16, 2018 7:46 PM, Timothy Baldridge <
As you say, this is a limitation in the code completer. In Cursive this
problem doesn't exist to this extent, when I type `(.get` the completer
responds with a list of methods and classes that could be completed to that
method, starting with classes in the namespace I'm currently editing.
I think we are saying the same thing here. I believe compliment (the
library CIDER/other clojure tooling uses for code completion) does what we
are describing (showing Java methods and fields from multiple Java types
that are imported into the namespace currently being edited (or type hinted
in locals/function definitions). My point is I want more -- I want the
completion list to only include methods and fields from the type I as a
developer know that I have.
MyType a = new MyType();
Now typing "a." yields just completions valid for MyType, and not 5 other
types I've used nearby.
Yes, this takes static analysis, or perhaps some indexing and
introspection. But changing the syntax is never really going to gain
traction here, as manually typing the name of every method just so my
editor can produce a list seems like me conforming to my tool rather than
my tool learning to work with me.
Just to make sure I'm completely clear -- I'm *not* advocating a change
to clojure lang -- only proposing a macro/library. The prototype macro I
wrote simply transforms the (Type:method ...) syntax into the (.
instance-expr member-symbol) that (.method ...) macroexpands into anyhow.
This is not intended to replace (.method obj args) notation.
I'm not sure what you mean by "manually typing the name of every method
just so my editor can produce a list".
The way this works is, you type "(MyType:" and get presented with a list
of completions that are *only* applicable to MyType -- there's no manually
typing lists of methods and fields anywhere. And, the way this works for
me in CIDER, I type "(My<TAB>" and I get "(MyType", then I add a ":", and
now I get completions just for MyType -- it also shows me the Java
signature of the methods as I highlight a potential completion. There's no
manually seeding the list anywhere...
The imported classes in the current namespace are stored in the mappings
attribute. It seems like an editor should be able to install hooks into
that and index the methods on the classes to provide this feedback.
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Namespace.java#L123
Yes I agree -- and I believe that's exactly what the existing tooling
already does -- I just want more precision.
Some of my motivation has been working with monstrously large class
libraries in Java. GIS libraries in Java are notoriously class/method
heavy and after importing many of these classes into a namespace,
completion that cannot narrow down to the exact type I'm using for the
reasons we agree on -- simply is painful to work with.
On Tue, Oct 16, 2018 at 1:26 PM 'somewhat-functional-programmer' via
Post by 'somewhat-functional-programmer' via Clojure
I apologize for diving into a solution in my first email -- let me give
a little more of the background as to what I am trying to accomplish.
I'm proposing an additional syntax for Java interop for the purpose of
allowing support for more precise code-completion of Java fields and
methods in Clojure tooling. The new syntax proposal is why I'm emailing
this list rather than directly going to the project owners of the projects
I've modified to support this. If there is high resistance to any
additional syntax for Java interop, there's not much reason to try to
convince the project owners of cider-nrepl and compliment to support
something just for my personal use :-).
Note also that when I say "propose additional syntax" I do not mean
"please add this to clojure lang" -- I've implemented this as a couple of
macros which should be the way the language normally gets extended. I just
want feedback and am trying to gauge interest, because if no one wants this
then it's not worth any of the additional effort to "publish" it -- I'll
just manage it as a utility library for myself.
- I want my development environment or REPL to know the type of the
Java object I'm operating on so it can limit the completion list to fields
or methods that are valid for that particular type.
(.get
I'd somehow like my environment to know that I want code
completion for a Java object of type "this.is.AType"
and as such, I would only see methods or fields starting with
".get" for that type
What happens for me now, is I see some Java methods and fields
starting with ".get" but on a number of Java objects (and not a complete
list at that).
(I believe the tooling looks through symbols in your namespace,
finds Java symbols, and starts pulling methods and fields from any of
them). Using only the text "(.get" the environment cannot know what I'm
trying to do.
Now the tooling could do static code analysis, it could look at the
surrounding top-level form, and say, what are the locals, are they
type-hinted, and only show me Java methods and fields from locals that have
type hints. Even this approach though, which is complex and error prone to
implement, still doesn't limit my list to just methods and fields of
"this.is.AType" unless I only have one type-hinted local symbol.
(Type:method obj? args)
"Type:method"
- Look up Type in my active namespace symbols (to find out if it's a
Java type I've imported) (or it could simply be fully qualified, both are
supported)
- Use reflection to find a list of candidate fields and methods
No static analysis required, simple to implement.
It's so simple to implement that I hastily pasted in my
proof-of-concept/prototype code in the first message -- which hopefully is
easy for folks to just try. I hope that people will try it and think: this
would be great if I were writing a lot of Java interop code -- or better
yet, give suggestions on how to improve it.
1) Install boot (or update it via "boot -u")
2) Make a new temp directory and navigate to it
3) Run the first command from my prior email in bash
4) Paste in the code snippet to the resulting rebel-readline repl
5) Try the limited completion right in rebel-readline
6) Connect using your cider-nrepl based tooling (CIDER, inf-clojure,
vim-fireplace, others?), and try it from one of those tools. I've only
tested with CIDER -- in my CIDER I get eldoc, help at symbol at point (I
wish this displayed javadoc inline), and better code completion.
7) Feedback! :-)
I also realize that this stuff is complex enough that there might be
some set of CIDER settings that solve this problem that I just simply don't
know about or didn't find. Love to hear about how others get better Java
interop code completion. Some of my clojure projects involve lots of Java
interop and this would have been sooooo nice to have while writing those
projects (in my opinion).
I think there are other advantages to this style syntax (besides
- Right now I have the jvm macro throw an error if it can't find the
java symbol -- so you would get a compile time error rather than just a
reflection warning about a non-existent java method.
- In addition to making easier to *write* Java interop, I think it
makes it easier to *read* Java interop
I immediately know the Java type while reading the code, no need to
hunt down the type annotations elsewhere. I've been writing Java since
Java 1.1 and I still like the reminder of what class a method is from.
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Tuesday, October 16, 2018 4:23 PM, Timothy Baldridge <
I don't understand why this is needed. Why can't cider code complete on
the normal method format? What's the problem this code is trying to solve?
Timothy
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with
your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with
your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send
For more options, visit https://groups.google.com/d/optout.
--
“One of the main causes of the fall of the Roman Empire was that–lacking
zero–they had no way to indicate successful termination of their C
programs.”
(Robert Firth)
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Didier
2018-10-17 04:34:07 UTC
Permalink
How does the new syntax help the tooling figure out the type?

(def var (SomeType.))
(.method var)

Or

(jvm (var.method))

I'm not sure how you narrow down to only the SomeType methods?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Colin Fleming
2018-10-17 08:40:50 UTC
Permalink
Cursive already allows this with no syntax changes, but it does require
reproducing Clojure's type inference in the editor. Cursive performs this
type inference, so (modulo bugs) it knows the types of basically everything
that the Clojure compiler does (and in fact in some situations can do
better, but I deliberately use just what the compiler knows right now).

This is used in various ways in completion. All of the cases that Aaron
enumerates above work, since Cursive can correctly propagate the types in
threading forms/doto etc (| is the caret):

(-> "my-string"
(.ch|)) ; <- Only String methods here

(-> (new MyType)
(.|)) ; <- MyType methods here

(def foo "hello")

(-> ^String foo
(.to|)) ; <- String completions here too, because of the type hint.
; Cursive can do better here even though the Clojure
compiler doesn't.
; Currently I restrict this, but completing could e.g.
automagically insert the type hint.

(let [foo "string"]
(-> foo (.to|)) ; <- No type hint required, since we know the type of foo

Additionally, Cursive supports the following:

(let [foo (ArrayList.)
iterator (.iterator foo)]
(.ne|)) ; <- Here, .next will be offered even though Iterator isn't
imported,
; because Cursive knows the types we have in scope at the
caret.

(let [foo (ArrayList.)]
(foo .i|)) ; <- Here, I've put the receiver first, then I'm completing
the method call.
; Since Cursive knows the type of foo, only ArrayList
methods will be completed.
; When I select a completion, Cursive will swap the two
around, like:
(let [foo (ArrayList.)]
(.iterator foo|))

I use this last one all the time, and it basically makes Clojure completion
as good as Java completion. Someone pointed out to me that this should
really use the existing dot syntax, like:

(let [foo (ArrayList.)]
(. foo it|)) ; <- completes to:

(let [foo (ArrayList.)]
(.iterator foo|))

But I haven't implemented that yet.

I don't do any of the more tricky stuff that IntelliJ does like
transitively searching across chained method calls based on the type, etc,
but that's just a time issue - there's nothing preventing Cursive from
doing that too. Also, offering completions from classes that are not
imported but are in scope makes a huge difference, even without any
switching trickiness.

Cheers,
Colin
Post by Didier
How does the new syntax help the tooling figure out the type?
(def var (SomeType.))
(.method var)
Or
(jvm (var.method))
I'm not sure how you narrow down to only the SomeType methods?
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-17 11:12:52 UTC
Permalink
I appreciate your detailed response, and you've certainly done great work with Cursive. I always recommend it to any Java programmer who is starting to learn Clojure. I will start to more seriously weigh the pros and consadditional of switching away from emacs. The cult of emacs has had a strong pull on me but your good work may mean I should leave it for Clojure work too (and not just for Java work :-)).

Cursive is doing much more in trying to match the Clojure compiler than I believe the compliment library has done to date. Part of me likes the readability of the syntax:
(String:charAt my-string 0)
over
(-> ^String my-string (.charAt 0))
But I realize that is much more subjective than anything else. I have to say, in delving into this even a little -- I appreciate the time you must have spent matching the Clojure compiler. I just remember looking at Kawa's syntax for Java method calls and thinking, wow, I wish Clojure had that -- so much more readable!

I like your last example a lot:
(foo .method) => getting turned into (.method foo) automatically by the editor.
I've actually looked at doing a similar thing with my macro -- basically using it in my editor, and then adding an nREPL middleware to transform it to the much beloved (.method obj args) notation. Since I subjectively like the readability of (String:charAt obj 0) better than (.charAt ^String obj 0) I didn't go that route in this discussion.

I'm curious though, why additional macro / slightly differences from "idiomatic" seems so important to avoid. I think something as simple as (String:charAt obj 0) notation would be pretty simple for a third syntax (since we already have two built in ways of doing it) -- and closer to the static method invocation syntax -- so really a third built-in syntax (Integer/parseInt "12"). But that's more a philosophical question I suppose :-). LISP curse anyone? :-)

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
Cursive already allows this with no syntax changes, but it does require reproducing Clojure's type inference in the editor. Cursive performs this type inference, so (modulo bugs) it knows the types of basically everything that the Clojure compiler does (and in fact in some situations can do better, but I deliberately use just what the compiler knows right now).
(-> "my-string"
(.ch|)) ; <- Only String methods here
(-> (new MyType)
(.|)) ; <- MyType methods here
(def foo "hello")
(-> ^String foo
(.to|)) ; <- String completions here too, because of the type hint.
; Cursive can do better here even though the Clojure compiler doesn't.
; Currently I restrict this, but completing could e.g. automagically insert the type hint.
(let [foo "string"]
(-> foo (.to|)) ; <- No type hint required, since we know the type of foo
(let [foo (ArrayList.)
iterator (.iterator foo)]
(.ne|)) ; <- Here, .next will be offered even though Iterator isn't imported,
; because Cursive knows the types we have in scope at the caret.
(let [foo (ArrayList.)]
(foo .i|)) ; <- Here, I've put the receiver first, then I'm completing the method call.
; Since Cursive knows the type of foo, only ArrayList methods will be completed.
(let [foo (ArrayList.)]
(.iterator foo|))
(let [foo (ArrayList.)]
(let [foo (ArrayList.)]
(.iterator foo|))
But I haven't implemented that yet.
I don't do any of the more tricky stuff that IntelliJ does like transitively searching across chained method calls based on the type, etc, but that's just a time issue - there's nothing preventing Cursive from doing that too. Also, offering completions from classes that are not imported but are in scope makes a huge difference, even without any switching trickiness.
Cheers,
Colin
Post by Didier
How does the new syntax help the tooling figure out the type?
(def var (SomeType.))
(.method var)
Or
(jvm (var.method))
I'm not sure how you narrow down to only the SomeType methods?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Colin Fleming
2018-10-20 10:13:20 UTC
Permalink
Post by 'somewhat-functional-programmer' via Clojure
I'm curious though, why additional macro / slightly differences from
"idiomatic" seems so important to avoid.
I can think of a couple of reasons off the top of my head:

1. It won't work with any tooling that's unaware of it, e.g. Cursive.
2. It's very verbose - it's even worse than Java, because instead of
annotating each variable once where it's declared you have to annotate
every method call, and those annotations remain forever in the code, not
just when you're writing it. This is true even when the type is trivially
obvious, e.g. (String:charAt "my-string" 0).Modern languages are moving
towards more sophisticated type inference for a reason - it's much more
comfortable.

Really, this feature is just to make editors work better. Why not do
something similar to what Cursive does, and allow the user to type
(String:ch|) . Then just show the method completions from String and when
the user selects "charAt" just convert to (.charAt |)? This is really a
feature to aid code writing after all, not reading. Then the feature
wouldn't require macros at all, it could be a purely Emacs thing.

On Wed, 17 Oct 2018 at 11:13, 'somewhat-functional-programmer' via Clojure <
Post by 'somewhat-functional-programmer' via Clojure
I appreciate your detailed response, and you've certainly done great work
with Cursive. I always recommend it to any Java programmer who is starting
to learn Clojure. I will start to more seriously weigh the pros and
consadditional of switching away from emacs. The cult of emacs has had a
strong pull on me but your good work may mean I should leave it for Clojure
work too (and not just for Java work :-)).
Cursive is doing much more in trying to match the Clojure compiler than I
believe the compliment library has done to date. Part of me likes the
(String:charAt my-string 0)
over
(-> ^String my-string (.charAt 0))
But I realize that is much more subjective than anything else. I have to
say, in delving into this even a little -- I appreciate the time you must
have spent matching the Clojure compiler. I just remember looking at
Kawa's syntax for Java method calls and thinking, wow, I wish Clojure had
that -- so much more readable!
(foo .method) => getting turned into (.method foo) automatically by the editor.
I've actually looked at doing a similar thing with my macro -- basically
using it in my editor, and then adding an nREPL middleware to transform it
to the much beloved (.method obj args) notation. Since I subjectively like
the readability of (String:charAt obj 0) better than (.charAt ^String obj
0) I didn't go that route in this discussion.
I'm curious though, why additional macro / slightly differences from
"idiomatic" seems so important to avoid. I think something as simple as
(String:charAt obj 0) notation would be pretty simple for a third syntax
(since we already have two built in ways of doing it) -- and closer to the
static method invocation syntax -- so really a third built-in syntax
(Integer/parseInt "12"). But that's more a philosophical question I
suppose :-). LISP curse anyone? :-)
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Wednesday, October 17, 2018 8:40 AM, Colin Fleming <
Cursive already allows this with no syntax changes, but it does require
reproducing Clojure's type inference in the editor. Cursive performs this
type inference, so (modulo bugs) it knows the types of basically everything
that the Clojure compiler does (and in fact in some situations can do
better, but I deliberately use just what the compiler knows right now).
This is used in various ways in completion. All of the cases that Aaron
enumerates above work, since Cursive can correctly propagate the types in
(-> "my-string"
(.ch|)) ; <- Only String methods here
(-> (new MyType)
(.|)) ; <- MyType methods here
(def foo "hello")
(-> ^String foo
(.to|)) ; <- String completions here too, because of the type hint.
; Cursive can do better here even though the Clojure compiler doesn't.
; Currently I restrict this, but completing could e.g.
automagically insert the type hint.
(let [foo "string"]
(-> foo (.to|)) ; <- No type hint required, since we know the type of foo
(let [foo (ArrayList.)
iterator (.iterator foo)]
(.ne|)) ; <- Here, .next will be offered even though Iterator isn't imported,
; because Cursive knows the types we have in scope at the caret.
(let [foo (ArrayList.)]
(foo .i|)) ; <- Here, I've put the receiver first, then I'm completing the method call.
; Since Cursive knows the type of foo, only ArrayList
methods will be completed.
(let [foo (ArrayList.)]
(.iterator foo|))
I use this last one all the time, and it basically makes Clojure
completion as good as Java completion. Someone pointed out to me that this
(let [foo (ArrayList.)]
(let [foo (ArrayList.)]
(.iterator foo|))
But I haven't implemented that yet.
I don't do any of the more tricky stuff that IntelliJ does like
transitively searching across chained method calls based on the type, etc,
but that's just a time issue - there's nothing preventing Cursive from
doing that too. Also, offering completions from classes that are not
imported but are in scope makes a huge difference, even without any
switching trickiness.
Cheers,
Colin
Post by Didier
How does the new syntax help the tooling figure out the type?
(def var (SomeType.))
(.method var)
Or
(jvm (var.method))
I'm not sure how you narrow down to only the SomeType methods?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-21 19:16:42 UTC
Permalink
Comments inline...

I appreciate the discussion to date!

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
Post by 'somewhat-functional-programmer' via Clojure
I'm curious though, why additional macro / slightly differences from "idiomatic" seems so important to avoid.
- It won't work with any tooling that's unaware of it, e.g. Cursive.
I agree -- my goal here isn't to add work everywhere. I think most Cursive users probably would care less if it isn't added to Cursive, and if there was an actual demand, at least the code to implement would already be done in another library, and a trivial amount of code.

One nice thing about cider-nrepl/compliment is that multiple tools use them -- I like having this code complete from rebel-readline for example, and in theory (I say this because I haven't tested it), it should work in vim-fireplace/vim-replant/etc.

I like using it from a rebel-readline REPL over SSH for example -- completely outside of emacs/CIDER.

I think this "style" of tooling (not my syntax -- but tooling that is always integrated/present in the clojure process) yields this type of benefit -- I can use it from wherever I run my code if I'd like, actually independent of any editor or development environment.
- It's very verbose - it's even worse than Java, because instead of annotating each variable once where it's declared you have to annotate every method call, and those annotations remain forever in the code, not just when you're writing it. This is true even when the type is trivially obvious, e.g. (String:charAt "my-string" 0).Modern languages are moving towards more sophisticated type inference for a reason - it's much more comfortable.
Really, this feature is just to make editors work better. Why not do something similar to what Cursive does, and allow the user to type (String:ch|) . Then just show the method completions from String and when the user selects "charAt" just convert to (.charAt |)? This is really a feature to aid code writing after all, not reading. Then the feature wouldn't require macros at all, it could be a purely Emacs thing.
I agree -- if I knew more about emacs lisp I would have gone down this route initially. However, my personal opinion is that this syntax does improve readability, at the price of additional verbosity -- a verbosity I personally don't mind while editing/writing because my editor helps me out. The syntax itself is similar to C++/Java lambda method references... Type:method. I love immediately seeing what class a method is from when looking over code where I'm unfamiliar with the Java types being used. But I agree -- this is subjective, and if I had more emacs lisp knowledge/time I would have thought much more about that route. My main goal posting here was:
- Hey try this syntax out, it's helped me tremendously with Java interop in CIDER, and hopefully elicit some feedback (and I appreciate yours)
I hope it's obvious from the idea of pasting code into a repl to try the feature that this isn't *done/released/supported/warrantied/whatever*, but really is the beginnings of an idea being explored. In fact, since I'm now starting to use this code more in one of my new interop-heavy projects, I've already made a couple of minor changes to it. It took me much longer to find a minimal boot command that used tools/deps/cli and a paste-able code-snippet than writing it in the first place :-).

I don't see how this is related to type-inferencing at all. Type-hinting the way we do in clojure is really a type declaration -- it isn't really trying to infer that say, something is a number based on usage (say, by being a participant in the '+' function --- on the contrary, if I want smart numerical "typing", I have to ^long or ^double). Clojure simply casts/boxes to whatever the interop function expects. If you're referring to the new Java syntax of 'var x = new blah();' -- I think that's mainly a reaction to How<Many<<Templates<Can<we<Have<and>Still<Be<readAble>>>>>...
I'd almost argue what we do in Clojure is more like gradual typing -- I start to type for performance/interop reasons (numerical computing or Java interop) but only sparingly. I don't doubt you are doing type inference in Cursive -- sounds like that's precisely how you are getting good completion lists.
Post by 'somewhat-functional-programmer' via Clojure
I appreciate your detailed response, and you've certainly done great work with Cursive. I always recommend it to any Java programmer who is starting to learn Clojure. I will start to more seriously weigh the pros and consadditional of switching away from emacs. The cult of emacs has had a strong pull on me but your good work may mean I should leave it for Clojure work too (and not just for Java work :-)).
(String:charAt my-string 0)
over
(-> ^String my-string (.charAt 0))
But I realize that is much more subjective than anything else. I have to say, in delving into this even a little -- I appreciate the time you must have spent matching the Clojure compiler. I just remember looking at Kawa's syntax for Java method calls and thinking, wow, I wish Clojure had that -- so much more readable!
(foo .method) => getting turned into (.method foo) automatically by the editor.
I've actually looked at doing a similar thing with my macro -- basically using it in my editor, and then adding an nREPL middleware to transform it to the much beloved (.method obj args) notation. Since I subjectively like the readability of (String:charAt obj 0) better than (.charAt ^String obj 0) I didn't go that route in this discussion.
I'm curious though, why additional macro / slightly differences from "idiomatic" seems so important to avoid. I think something as simple as (String:charAt obj 0) notation would be pretty simple for a third syntax (since we already have two built in ways of doing it) -- and closer to the static method invocation syntax -- so really a third built-in syntax (Integer/parseInt "12"). But that's more a philosophical question I suppose :-). LISP curse anyone? :-)
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
Cursive already allows this with no syntax changes, but it does require reproducing Clojure's type inference in the editor. Cursive performs this type inference, so (modulo bugs) it knows the types of basically everything that the Clojure compiler does (and in fact in some situations can do better, but I deliberately use just what the compiler knows right now).
(-> "my-string"
(.ch|)) ; <- Only String methods here
(-> (new MyType)
(.|)) ; <- MyType methods here
(def foo "hello")
(-> ^String foo
(.to|)) ; <- String completions here too, because of the type hint.
; Cursive can do better here even though the Clojure compiler doesn't.
; Currently I restrict this, but completing could e.g. automagically insert the type hint.
(let [foo "string"]
(-> foo (.to|)) ; <- No type hint required, since we know the type of foo
(let [foo (ArrayList.)
iterator (.iterator foo)]
(.ne|)) ; <- Here, .next will be offered even though Iterator isn't imported,
; because Cursive knows the types we have in scope at the caret.
(let [foo (ArrayList.)]
(foo .i|)) ; <- Here, I've put the receiver first, then I'm completing the method call.
; Since Cursive knows the type of foo, only ArrayList methods will be completed.
(let [foo (ArrayList.)]
(.iterator foo|))
(let [foo (ArrayList.)]
(let [foo (ArrayList.)]
(.iterator foo|))
But I haven't implemented that yet.
I don't do any of the more tricky stuff that IntelliJ does like transitively searching across chained method calls based on the type, etc, but that's just a time issue - there's nothing preventing Cursive from doing that too. Also, offering completions from classes that are not imported but are in scope makes a huge difference, even without any switching trickiness.
Cheers,
Colin
Post by Didier
How does the new syntax help the tooling figure out the type?
(def var (SomeType.))
(.method var)
Or
(jvm (var.method))
I'm not sure how you narrow down to only the SomeType methods?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-17 11:16:32 UTC
Permalink
It's not really:
(jvm (var.method))
but
(jvm (JavaType:method var))

Because the completion engines get "JavaType:" as a prefix, they can look up the type via reflection and present a list of methods for *just* that type.

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
Post by Didier
How does the new syntax help the tooling figure out the type?
(def var (SomeType.))
(.method var)
Or
(jvm (var.method))
I'm not sure how you narrow down to only the SomeType methods?
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-16 19:45:08 UTC
Permalink
Comments inline...

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
Post by 'Tatu Tarvainen' via Clojure
Nice. I like the syntax, I'll try it out.
Thanks -- let me know if the boot command doesn't work... I had to update boot on one of my boxes to get it to work ("boot -u" -- to 2.8.2 I think).
Post by 'Tatu Tarvainen' via Clojure
But it seems unlikely to me that the interop forms would be changed in core at this late stage.
I agree -- I probably should have worded "propose new syntax" differently -- this was never intended to be a candidate for clojure lang -- it was always intended to be a macro similar to the prototype code I put in my last message.
Post by 'Tatu Tarvainen' via Clojure
As metadata tags can be added to symbols, could we write (^String .charAt "test-string" 0)
It doesn't look as nice as your proposed syntax, but is possible without modifications.
Interesting -- I don't believe I'd get the full "(^String .prefix" in the place in the compliment library where I made my addition (as the "prefix") -- but I would get the "context" (from some but potentially not all clojure tooling) -- the context being the surrounding top-level form which would include the "^String" -- so in theory it would be possible for some clojure tooling to use this to accomplish the same thing.

I think it would look odd for those places where you still had to type hint to avoid reflection:
(defn avoid-reflection-warning-by-doing-this [a]
(^InputStream .read ^InputStream a))

(The macro on the other hand automatically adds the type hint to avoid reflection)
Post by 'Tatu Tarvainen' via Clojure
Post by 'somewhat-functional-programmer' via Clojure
I'd like to share an idea and prototype code for better Java code completion in CIDER. While my main development environment is CIDER, the small modifications I made to support this idea were both to cider-nrepl and compliment -- which are both used by other Clojure tooling besides CIDER -- so maybe this is immediately more widely applicable.
(String:charAt "my-string" 0) => \m
(Integer:parseInt "12") => 12
(possibly.fully.qualified.YourClassName:method this args)
(jvm (String:charAt "my-string" 0))
(jvm
(lots of code)
(JavaClass:method ...)
(more code)
(AnotherJavaClass:method ...))
(jvm/String:charAt "my-string" 0)
This will also work with the one-off test code I'm including here for folks to see what they think.
I actually like the syntax (though I wish I didn't have to wrap it in a jvm macro -- though if this actually idea was worth fully implementing, I'd imagine having new let or function macros so you don't even have to sprinkle "jvm" macros in code much at all).
- The jvm macro uses reflection to find the appropriate method at compile time, and as such, you get a compile error if the method cannot be found.
- This is a downside if you *want* reflection, but this of course doesn't preclude using the normal (.method obj args) notation.
- Though not implemented in my toy code here, you could also pass String:charAt as a clojure function -- assuming there were no overloads of the same arity.
So, I'm hoping you will try this out. Two things to copy/paste -- one is a boot command, the other is the 100-200 lines of clojure that implements a prototype of this.
# Run this somewhere where you can make an empty source directory,
# something fails in boot-tools-deps if you don't have one
# (much appreciate boot-tools-deps -- as cider-nrepl really needs to
# be a git dep for my purpose here since it's run through mranderson for its normal distro)
mkdir src && \
boot -d seancorfield/boot-tools-deps:0.4.6 \
-d compliment:0.3.6 -d cider/orchard:0.3.1 \
-d com.rpl/specter:1.1.1 -d com.taoensso/timbre:4.10.0 \
-d com.bhauman/rebel-readline:0.1.4 \
-d nrepl/nrepl:0.4.5 \
deps --config-data \
'{:deps {cider/cider-nrepl {:git/url "https://github.com/clojure-emacs/cider-nrepl.git" :sha "b2c0b920d762fdac2f8210805df2055af63f2eb1"}}}' \
call -f rebel-readline.main/-main
(require 'cider.nrepl.middleware.info)
(ns java-interop.core
(:require
[taoensso.timbre :as timbre
:refer [log trace debug info warn error fatal report
logf tracef debugf infof warnf errorf fatalf reportf
spy get-env]]
[clojure.reflect :as reflect]
[clojure.string :as s :refer [includes?]]
[com.rpl.specter :as sp]
[orchard.java :as java]))
(defn specific-class-member? [prefix]
;; NOTE: get a proper java class identifier here
(when-let [prefix (if (symbol? prefix)
(name prefix)
prefix)]
(and
(not (.startsWith prefix ":"))
(not (includes? prefix "::"))
(includes? prefix ":"))))
(def select-j-path
(sp/recursive-path
[] p
(sp/cond-path
#(and (seq? %) (specific-class-member? (first %)))
[(sp/continue-then-stay sp/ALL-WITH-META p)]
map? (sp/multi-path
[sp/MAP-KEYS p]
[sp/MAP-VALS p])
vector? [sp/ALL-WITH-META p]
seq? [sp/ALL-WITH-META p])))
(defmacro j [[s & [obj & args]]]
;; FIXME: Add better error checking later
;; FIXME: Java fields can have the same name as a method
(let [[clazz-str method-or-field & too-many-semis] (.split (name s) ":")
method-or-field-sym (symbol method-or-field)
clazz-sym (symbol clazz-str)]
(if-let [{:keys [flags return-type]} (first
(filter
#(= (:name %) method-or-field-sym)
(:members
(reflect/reflect
(ns-resolve *ns* clazz-sym)
:ancestors true))))]
(cond
(contains? flags :static) (concat
`(. ~clazz-sym ~method-or-field-sym)
(if obj
`(~obj))
args)
:else
(concat
`(. ~(if (symbol? obj)
(with-meta
obj
{:tag clazz-sym})
obj)
~(symbol method-or-field))
args))
(throw (ex-info "Method or field does not exist in class."
{:method method-or-field-sym
:class clazz-sym})))))
(defmacro jvm [& body]
(concat
`(do)
(map
#(sp/transform
select-j-path
(fn [form]
`(j ~form))
%)
body)))
;; for compliment code complete
(in-ns 'compliment.sources.class-members)
(require 'java-interop.core)
(defn members-candidates
"Returns a list of Java non-static fields and methods candidates."
[prefix ns context]
(cond
(class-member-symbol? prefix)
(let [prefix (subs prefix 1)
inparts? (re-find #"[A-Z]" prefix)
klass (try-get-object-class ns context)]
(for [[member-name members] (get-all-members ns)
:when (if inparts?
(camel-case-matches? prefix member-name)
(.startsWith ^String member-name prefix))
:when
(or (not klass)
(some #(= klass (.getDeclaringClass ^Member %)) members))]
{:candidate (str "." member-name)
:type (if (instance? Method (first members))
:method :field)}))
(java-interop.core/specific-class-member? prefix)
(let [sym (symbol prefix)
[clazz-str member-str & too-many-semis] (.split (name sym) #_prefix ":")]
(when (not too-many-semis)
(when-let [clazz
(resolve-class ns (symbol clazz-str))]
(->>
(clojure.reflect/reflect clazz :ancestors true)
(:members)
(filter #(and
;; public
(contains? (:flags %) :public)
;; but not a constructor
(not (and (not (:return-type %)) (:parameter-types %)))
;; and of course, the name must match
(or
(clojure.string/blank? member-str)
(.startsWith (str (:name %)) member-str))))
(map
(fn [{:keys [name type return-type]}]
{:candidate (str (when-let [n (namespace sym)]
(str (namespace sym) "/")) clazz-str ":" name)
:type (if return-type
:method
:field)}))))))))
;; for eldoc support in cider
(in-ns 'cider.nrepl.middleware.info)
(require 'orchard.info)
(defn java-special-sym [ns sym]
(let [sym-str (name sym)]
(if (clojure.string/includes? sym-str ":")
(when-let [[class member & too-many-semis] (.split sym-str ":")]
(if (and class
member
(not too-many-semis))
(when-let [resolved-clazz-sym
(some->>
(symbol class)
^Class (compliment.utils/resolve-class ns)
(.getName)
(symbol))]
[resolved-clazz-sym
(symbol member)]))))))
(defn info [{:keys [ns symbol class member] :as msg}]
(let [[ns symbol class member] (map orchard.misc/as-sym [ns symbol class member])]
(if-let [cljs-env (cider.nrepl.middleware.util.cljs/grab-cljs-env msg)]
(info-cljs cljs-env symbol ns)
(let [var-info (cond (and ns symbol) (or
(orchard.info/info ns symbol)
(when-let [[clazz member]
(java-special-sym ns symbol)]
(orchard.info/info-java clazz member)))
(and class member) (orchard.info/info-java class member)
:else (throw (Exception.
"Either \"symbol\", or (\"class\", \"member\") must be supplied")))
;; we have to use the resolved (real) namespace and name here
see-also (orchard.info/see-also (:ns var-info) (:name var-info))]
(if (seq see-also)
(merge {:see-also see-also} var-info)
var-info)))))
;; cider blows up if we don't have a project.clj file for it to read the version
;; string from
(ns cider.nrepl.version
;; We require print-method here because `cider.nrepl.version`
;; namespace is used by every connection.
(:require [cider.nrepl.print-method]
[clojure.java.io :as io]))
#_(def version-string
"The current version for cider-nrepl as a string."
(-> (or (io/resource "cider/cider-nrepl/project.clj")
"project.clj")
slurp
read-string
(nth 2)))
(def version-string "0.19.0-SNAPSHOT")
(def version
"Current version of CIDER nREPL as a map.
Map of :major, :minor, :incremental, :qualifier,
and :version-string."
(assoc (->> version-string
(re-find #"(\d+)\.(\d+)\.(\d+)-?(.*)")
rest
(map #(try (Integer/parseInt %) (catch Exception e nil)))
(zipmap [:major :minor :incremental :qualifier]))
:version-string version-string))
(defn cider-version-reply
"Returns CIDER-nREPL's version as a map which contains `:major`,
`:minor`, `:incremental`, and `:qualifier` keys, just as
`*clojure-version*` does."
[msg]
{:cider-version version})
(in-ns 'boot.user)
(require 'nrepl.server)
(defn nrepl-handler []
(require 'cider.nrepl)
(ns-resolve 'cider.nrepl 'cider-nrepl-handler))
(nrepl.server/start-server :port 7888 :handler (nrepl-handler))
(require '[java-interop.core :refer [jvm]])
;; NOTE: Code completion works in rebel-readline,
;; but it limits how many completions are shown at once
;; Try CIDER (you have an nrepl instance running now localhost:7888)
;; Eldoc also works in cider
;;
;; example
(jvm
[(Integer:parseInt "12") (String:charAt "test-string" 0)])
You should now have an nrepl server running on localhost:7888 which you can connect to from CIDER. You can try the completion (though not documentation) right in rebel-readline's repl.
So give it a try.... you'll notice static fields aren't handled perfectly (easy fix, but again I really am looking for feedback on the concept, and am wondering who would use it etc).
(jvm (Integer:MAX_VALUE))
I think the code completion + eldoc in CIDER is a productivity boost for sure.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Matching Socks
2018-10-19 09:58:38 UTC
Permalink
The cost/benefit is hard to work out. Look ahead 7 years - we would have
the ceremonial (jvm... (MyType:baz...)) in some places and the good old
elegant (.baz ...) in others, and the tooling will work just as well with
it. On the other hand, if you routinely do tons of interop with a wide
variety of classes whose method names are subtly distinct and mostly 40
letters or more, then (jvm...) will seem like genius! Nonetheless, I hope
your work will inspire code completers to go the extra mile, once and for
all. In the meantime, you could wrap that dreadful interop with a library
whose method names are shorter.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-10-21 19:23:50 UTC
Permalink
I agree :-). For me the cost/benefit decision is easy -- I'm using it in a new interop-heavy project. I posted it here in case others who used cider-nrepl based tooling may also potentially benefit -- if enough interest maybe I'd take the extra time to publish it as a library.

Often times I have had small macros / functions that I imagine would be of use to others but never know how to share them. I personally have a bad reaction against pulling in a named dependency for 1 or 2 functions -- how to share such things? I bet most developers in a lisp start to have their own utility libraries. I've always imagined there would be much for me to learn by seeing those libraries from seasonsed lispers.

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
The cost/benefit is hard to work out. Look ahead 7 years - we would have the ceremonial (jvm... (MyType:baz...)) in some places and the good old elegant (.baz ...) in others, and the tooling will work just as well with it. On the other hand, if you routinely do tons of interop with a wide variety of classes whose method names are subtly distinct and mostly 40 letters or more, then (jvm...) will seem like genius! Nonetheless, I hope your work will inspire code completers to go the extra mile, once and for all. In the meantime, you could wrap that dreadful interop with a library whose method names are shorter.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Alexander Yakushev
2018-11-25 17:53:06 UTC
Permalink
To anyone who's interested in having precise completion for Java classes –
a feature like this is available in the latest CIDER
snapshot: https://twitter.com/unlog1c/status/1066748173094453248. You can
get a filtered completion by prepending a type tag to the next symbol.

On Friday, October 12, 2018 at 3:22:37 AM UTC+3,
Post by 'somewhat-functional-programmer' via Clojure
I'd like to share an idea and prototype code for better Java code
completion in CIDER. While my main development environment is CIDER, the
small modifications I made to support this idea were both to cider-nrepl
and compliment -- which are both used by other Clojure tooling besides
CIDER -- so maybe this is immediately more widely applicable.
In an effort to make it easier on the tooling, I'm using a slightly
different syntax for calling Java methods. My inspiration is Kawa scheme,
(String:charAt "my-string" 0) => \m
(Integer:parseInt "12") => 12
(possibly.fully.qualified.YourClassName:method this args)
(jvm (String:charAt "my-string" 0))
(jvm
(lots of code)
(JavaClass:method ...)
(more code)
(AnotherJavaClass:method ...))
The jvm macro will transform any symbol it finds in the calling position
of a list that follows the ClassName:method convention. I was thinking
maybe of limiting it to just a particular namespace to absolutely prevent
(jvm/String:charAt "my-string" 0)
This will also work with the one-off test code I'm including here for
folks to see what they think.
I actually like the syntax (though I wish I didn't have to wrap it in a
jvm macro -- though if this actually idea was worth fully implementing, I'd
imagine having new let or function macros so you don't even have to
sprinkle "jvm" macros in code much at all).
There is one additional advantages to this style of Java interop besides
- The jvm macro uses reflection to find the appropriate method at
compile time, and as such, you get a compile error if the method cannot be
found.
- This is a downside if you *want* reflection, but this of course
doesn't preclude using the normal (.method obj args) notation.
- Though not implemented in my toy code here, you could also pass
String:charAt as a clojure function -- assuming there were no overloads of
the same arity.
So, I'm hoping you will try this out. Two things to copy/paste -- one is
a boot command, the other is the 100-200 lines of clojure that implements a
prototype of this.
This command pulls the necessary dependencies as well as starts up the
rebel-readline repl (which is fantastic tool, and it also uses compliment
# Run this somewhere where you can make an empty source directory,
# something fails in boot-tools-deps if you don't have one
# (much appreciate boot-tools-deps -- as cider-nrepl really needs to
# be a git dep for my purpose here since it's run through mranderson
for its normal distro)
mkdir src && \
boot -d seancorfield/boot-tools-deps:0.4.6 \
-d compliment:0.3.6 -d cider/orchard:0.3.1 \
-d com.rpl/specter:1.1.1 -d com.taoensso/timbre:4.10.0 \
-d com.bhauman/rebel-readline:0.1.4 \
-d nrepl/nrepl:0.4.5 \
deps --config-data \
'{:deps {cider/cider-nrepl {:git/url "
https://github.com/clojure-emacs/cider-nrepl.git" :sha
"b2c0b920d762fdac2f8210805df2055af63f2eb1"}}}' \
call -f rebel-readline.main/-main
(require 'cider.nrepl.middleware.info)
(ns java-interop.core
(:require
[taoensso.timbre :as timbre
:refer [log trace debug info warn error fatal report
logf tracef debugf infof warnf errorf fatalf reportf
spy get-env]]
[clojure.reflect :as reflect]
[clojure.string :as s :refer [includes?]]
[com.rpl.specter :as sp]
[orchard.java :as java]))
(defn specific-class-member? [prefix]
;; NOTE: get a proper java class identifier here
(when-let [prefix (if (symbol? prefix)
(name prefix)
prefix)]
(and
(not (.startsWith prefix ":"))
(not (includes? prefix "::"))
(includes? prefix ":"))))
(def select-j-path
(sp/recursive-path
[] p
(sp/cond-path
#(and (seq? %) (specific-class-member? (first %)))
[(sp/continue-then-stay sp/ALL-WITH-META p)]
map? (sp/multi-path
[sp/MAP-KEYS p]
[sp/MAP-VALS p])
vector? [sp/ALL-WITH-META p]
seq? [sp/ALL-WITH-META p])))
(defmacro j [[s & [obj & args]]]
;; FIXME: Add better error checking later
;; FIXME: Java fields can have the same name as a method
(let [[clazz-str method-or-field & too-many-semis] (.split (name s) ":")
method-or-field-sym (symbol method-or-field)
clazz-sym (symbol clazz-str)]
(if-let [{:keys [flags return-type]} (first
(filter
#(= (:name %)
method-or-field-sym)
(:members
(reflect/reflect
(ns-resolve *ns* clazz-sym)
:ancestors true))))]
(cond
(contains? flags :static) (concat
`(. ~clazz-sym ~method-or-field-sym)
(if obj
`(~obj))
args)
:else
(concat
`(. ~(if (symbol? obj)
(with-meta
obj
{:tag clazz-sym})
obj)
~(symbol method-or-field))
args))
(throw (ex-info "Method or field does not exist in class."
{:method method-or-field-sym
:class clazz-sym})))))
(defmacro jvm [& body]
(concat
`(do)
(map
#(sp/transform
select-j-path
(fn [form]
`(j ~form))
%)
body)))
;; for compliment code complete
(in-ns 'compliment.sources.class-members)
(require 'java-interop.core)
(defn members-candidates
"Returns a list of Java non-static fields and methods candidates."
[prefix ns context]
(cond
(class-member-symbol? prefix)
(let [prefix (subs prefix 1)
inparts? (re-find #"[A-Z]" prefix)
klass (try-get-object-class ns context)]
(for [[member-name members] (get-all-members ns)
:when (if inparts?
(camel-case-matches? prefix member-name)
(.startsWith ^String member-name prefix))
:when
(or (not klass)
(some #(= klass (.getDeclaringClass ^Member %)) members))]
{:candidate (str "." member-name)
:type (if (instance? Method (first members))
:method :field)}))
(java-interop.core/specific-class-member? prefix)
(let [sym (symbol prefix)
[clazz-str member-str & too-many-semis] (.split (name sym) #_prefix ":")]
(when (not too-many-semis)
(when-let [clazz
(resolve-class ns (symbol clazz-str))]
(->>
(clojure.reflect/reflect clazz :ancestors true)
(:members)
(filter #(and
;; public
(contains? (:flags %) :public)
;; but not a constructor
(not (and (not (:return-type %)) (:parameter-types %)))
;; and of course, the name must match
(or
(clojure.string/blank? member-str)
(.startsWith (str (:name %)) member-str))))
(map
(fn [{:keys [name type return-type]}]
{:candidate (str (when-let [n (namespace sym)]
(str (namespace sym) "/")) clazz-str ":" name)
:type (if return-type
:method
:field)}))))))))
;; for eldoc support in cider
(in-ns 'cider.nrepl.middleware.info)
(require 'orchard.info)
(defn java-special-sym [ns sym]
(let [sym-str (name sym)]
(if (clojure.string/includes? sym-str ":")
(when-let [[class member & too-many-semis] (.split sym-str ":")]
(if (and class
member
(not too-many-semis))
(when-let [resolved-clazz-sym
(some->>
(symbol class)
^Class (compliment.utils/resolve-class ns)
(.getName)
(symbol))]
[resolved-clazz-sym
(symbol member)]))))))
(defn info [{:keys [ns symbol class member] :as msg}]
(let [[ns symbol class member] (map orchard.misc/as-sym [ns symbol class member])]
(if-let [cljs-env (cider.nrepl.middleware.util.cljs/grab-cljs-env msg)]
(info-cljs cljs-env symbol ns)
(let [var-info (cond (and ns symbol) (or
(orchard.info/info ns symbol)
(when-let [[clazz member]
(java-special-sym ns symbol)]
(orchard.info/info-java clazz member)))
(and class member) (orchard.info/info-java class member)
:else (throw (Exception.
"Either \"symbol\", or
(\"class\", \"member\") must be supplied")))
;; we have to use the resolved (real) namespace and name here
see-also (orchard.info/see-also (:ns var-info) (:name var-info))]
(if (seq see-also)
(merge {:see-also see-also} var-info)
var-info)))))
;; cider blows up if we don't have a project.clj file for it to read the version
;; string from
(ns cider.nrepl.version
;; We require print-method here because `cider.nrepl.version`
;; namespace is used by every connection.
(:require [cider.nrepl.print-method]
[clojure.java.io :as io]))
#_(def version-string
"The current version for cider-nrepl as a string."
(-> (or (io/resource "cider/cider-nrepl/project.clj")
"project.clj")
slurp
read-string
(nth 2)))
(def version-string "0.19.0-SNAPSHOT")
(def version
"Current version of CIDER nREPL as a map.
Map of :major, :minor, :incremental, :qualifier,
and :version-string."
(assoc (->> version-string
(re-find #"(\d+)\.(\d+)\.(\d+)-?(.*)")
rest
(map #(try (Integer/parseInt %) (catch Exception e nil)))
(zipmap [:major :minor :incremental :qualifier]))
:version-string version-string))
(defn cider-version-reply
"Returns CIDER-nREPL's version as a map which contains `:major`,
`:minor`, `:incremental`, and `:qualifier` keys, just as
`*clojure-version*` does."
[msg]
{:cider-version version})
(in-ns 'boot.user)
(require 'nrepl.server)
(defn nrepl-handler []
(require 'cider.nrepl)
(ns-resolve 'cider.nrepl 'cider-nrepl-handler))
(nrepl.server/start-server :port 7888 :handler (nrepl-handler))
(require '[java-interop.core :refer [jvm]])
;; NOTE: Code completion works in rebel-readline,
;; but it limits how many completions are shown at once
;; Try CIDER (you have an nrepl instance running now localhost:7888)
;; Eldoc also works in cider
;;
;; example
(jvm
[(Integer:parseInt "12") (String:charAt "test-string" 0)])
You should now have an nrepl server running on localhost:7888 which you
can connect to from CIDER. You can try the completion (though not
documentation) right in rebel-readline's repl.
So give it a try.... you'll notice static fields aren't handled perfectly
(easy fix, but again I really am looking for feedback on the concept, and
am wondering who would use it etc).
(jvm (Integer:MAX_VALUE))
I think the code completion + eldoc in CIDER is a productivity boost for sure.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
'somewhat-functional-programmer' via Clojure
2018-11-27 01:40:44 UTC
Permalink
Very cool! By the way, thanks for all the work you have put in to compliment. Sometimes it's hard to know when/how to thank people. I myself am all too often guilty of, "Thanks, can I have a new feature or bug fix?"

You, Mr. Emerick, Mr. Batsov -- and many others -- thanks!

I'd start a thanksgiving thread for clojure, but I'm afraid my list would be quite long, and sort of sound presumptuous, as if I'd accomplished something worthy of thanking the many folks whose work made it possible. So how about this --
Thanks to all the different folks in the community for sharing your work, whether I've used it or not, I've learned many things from:
- Presentations by Rich Hickey
- The Joy of Clojure
- CIDER
- Figwheel and ClojureScript
- core.async, manifold, ring, sente, timbre, specter, seesaw, ...

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
To anyone who's interested in having precise completion for Java classes – a feature like this is available in the latest CIDER snapshot: https://twitter.com/unlog1c/status/1066748173094453248. You can get a filtered completion by prepending a type tag to the next symbol.
Post by 'somewhat-functional-programmer' via Clojure
I'd like to share an idea and prototype code for better Java code completion in CIDER. While my main development environment is CIDER, the small modifications I made to support this idea were both to cider-nrepl and compliment -- which are both used by other Clojure tooling besides CIDER -- so maybe this is immediately more widely applicable.
(String:charAt "my-string" 0) => \m
(Integer:parseInt "12") => 12
(possibly.fully.qualified.YourClassName:method this args)
(jvm (String:charAt "my-string" 0))
(jvm
(lots of code)
(JavaClass:method ...)
(more code)
(AnotherJavaClass:method ...))
(jvm/String:charAt "my-string" 0)
This will also work with the one-off test code I'm including here for folks to see what they think.
I actually like the syntax (though I wish I didn't have to wrap it in a jvm macro -- though if this actually idea was worth fully implementing, I'd imagine having new let or function macros so you don't even have to sprinkle "jvm" macros in code much at all).
- The jvm macro uses reflection to find the appropriate method at compile time, and as such, you get a compile error if the method cannot be found.
- This is a downside if you *want* reflection, but this of course doesn't preclude using the normal (.method obj args) notation.
- Though not implemented in my toy code here, you could also pass String:charAt as a clojure function -- assuming there were no overloads of the same arity.
So, I'm hoping you will try this out. Two things to copy/paste -- one is a boot command, the other is the 100-200 lines of clojure that implements a prototype of this.
# Run this somewhere where you can make an empty source directory,
# something fails in boot-tools-deps if you don't have one
# (much appreciate boot-tools-deps -- as cider-nrepl really needs to
# be a git dep for my purpose here since it's run through mranderson for its normal distro)
mkdir src && \
boot -d seancorfield/boot-tools-deps:0.4.6 \
-d compliment:0.3.6 -d cider/orchard:0.3.1 \
-d com.rpl/specter:1.1.1 -d com.taoensso/timbre:4.10.0 \
-d com.bhauman/rebel-readline:0.1.4 \
-d nrepl/nrepl:0.4.5 \
deps --config-data \
'{:deps {cider/cider-nrepl {:git/url "https://github.com/clojure-emacs/cider-nrepl.git" :sha "b2c0b920d762fdac2f8210805df2055af63f2eb1"}}}' \
call -f rebel-readline.main/-main
(require 'cider.nrepl.middleware.info)
(ns java-interop.core
(:require
[taoensso.timbre :as timbre
:refer [log trace debug info warn error fatal report
logf tracef debugf infof warnf errorf fatalf reportf
spy get-env]]
[clojure.reflect :as reflect]
[clojure.string :as s :refer [includes?]]
[com.rpl.specter :as sp]
[orchard.java :as java]))
(defn specific-class-member? [prefix]
;; NOTE: get a proper java class identifier here
(when-let [prefix (if (symbol? prefix)
(name prefix)
prefix)]
(and
(not (.startsWith prefix ":"))
(not (includes? prefix "::"))
(includes? prefix ":"))))
(def select-j-path
(sp/recursive-path
[] p
(sp/cond-path
#(and (seq? %) (specific-class-member? (first %)))
[(sp/continue-then-stay sp/ALL-WITH-META p)]
map? (sp/multi-path
[sp/MAP-KEYS p]
[sp/MAP-VALS p])
vector? [sp/ALL-WITH-META p]
seq? [sp/ALL-WITH-META p])))
(defmacro j [[s & [obj & args]]]
;; FIXME: Add better error checking later
;; FIXME: Java fields can have the same name as a method
(let [[clazz-str method-or-field & too-many-semis] (.split (name s) ":")
method-or-field-sym (symbol method-or-field)
clazz-sym (symbol clazz-str)]
(if-let [{:keys [flags return-type]} (first
(filter
#(= (:name %) method-or-field-sym)
(:members
(reflect/reflect
(ns-resolve *ns* clazz-sym)
:ancestors true))))]
(cond
(contains? flags :static) (concat
`(. ~clazz-sym ~method-or-field-sym)
(if obj
`(~obj))
args)
:else
(concat
`(. ~(if (symbol? obj)
(with-meta
obj
{:tag clazz-sym})
obj)
~(symbol method-or-field))
args))
(throw (ex-info "Method or field does not exist in class."
{:method method-or-field-sym
:class clazz-sym})))))
(defmacro jvm [& body]
(concat
`(do)
(map
#(sp/transform
select-j-path
(fn [form]
`(j ~form))
%)
body)))
;; for compliment code complete
(in-ns 'compliment.sources.class-members)
(require 'java-interop.core)
(defn members-candidates
"Returns a list of Java non-static fields and methods candidates."
[prefix ns context]
(cond
(class-member-symbol? prefix)
(let [prefix (subs prefix 1)
inparts? (re-find #"[A-Z]" prefix)
klass (try-get-object-class ns context)]
(for [[member-name members] (get-all-members ns)
:when (if inparts?
(camel-case-matches? prefix member-name)
(.startsWith ^String member-name prefix))
:when
(or (not klass)
(some #(= klass (.getDeclaringClass ^Member %)) members))]
{:candidate (str "." member-name)
:type (if (instance? Method (first members))
:method :field)}))
(java-interop.core/specific-class-member? prefix)
(let [sym (symbol prefix)
[clazz-str member-str & too-many-semis] (.split (name sym) #_prefix ":")]
(when (not too-many-semis)
(when-let [clazz
(resolve-class ns (symbol clazz-str))]
(->>
(clojure.reflect/reflect clazz :ancestors true)
(:members)
(filter #(and
;; public
(contains? (:flags %) :public)
;; but not a constructor
(not (and (not (:return-type %)) (:parameter-types %)))
;; and of course, the name must match
(or
(clojure.string/blank? member-str)
(.startsWith (str (:name %)) member-str))))
(map
(fn [{:keys [name type return-type]}]
{:candidate (str (when-let [n (namespace sym)]
(str (namespace sym) "/")) clazz-str ":" name)
:type (if return-type
:method
:field)}))))))))
;; for eldoc support in cider
(in-ns 'cider.nrepl.middleware.info)
(require 'orchard.info)
(defn java-special-sym [ns sym]
(let [sym-str (name sym)]
(if (clojure.string/includes? sym-str ":")
(when-let [[class member & too-many-semis] (.split sym-str ":")]
(if (and class
member
(not too-many-semis))
(when-let [resolved-clazz-sym
(some->>
(symbol class)
^Class (compliment.utils/resolve-class ns)
(.getName)
(symbol))]
[resolved-clazz-sym
(symbol member)]))))))
(defn info [{:keys [ns symbol class member] :as msg}]
(let [[ns symbol class member] (map orchard.misc/as-sym [ns symbol class member])]
(if-let [cljs-env (cider.nrepl.middleware.util.cljs/grab-cljs-env msg)]
(info-cljs cljs-env symbol ns)
(let [var-info (cond (and ns symbol) (or
(orchard.info/info ns symbol)
(when-let [[clazz member]
(java-special-sym ns symbol)]
(orchard.info/info-java clazz member)))
(and class member) (orchard.info/info-java class member)
:else (throw (Exception.
"Either \"symbol\", or (\"class\", \"member\") must be supplied")))
;; we have to use the resolved (real) namespace and name here
see-also (orchard.info/see-also (:ns var-info) (:name var-info))]
(if (seq see-also)
(merge {:see-also see-also} var-info)
var-info)))))
;; cider blows up if we don't have a project.clj file for it to read the version
;; string from
(ns cider.nrepl.version
;; We require print-method here because `cider.nrepl.version`
;; namespace is used by every connection.
(:require [cider.nrepl.print-method]
[clojure.java.io :as io]))
#_(def version-string
"The current version for cider-nrepl as a string."
(-> (or (io/resource "cider/cider-nrepl/project.clj")
"project.clj")
slurp
read-string
(nth 2)))
(def version-string "0.19.0-SNAPSHOT")
(def version
"Current version of CIDER nREPL as a map.
Map of :major, :minor, :incremental, :qualifier,
and :version-string."
(assoc (->> version-string
(re-find #"(\d+)\.(\d+)\.(\d+)-?(.*)")
rest
(map #(try (Integer/parseInt %) (catch Exception e nil)))
(zipmap [:major :minor :incremental :qualifier]))
:version-string version-string))
(defn cider-version-reply
"Returns CIDER-nREPL's version as a map which contains `:major`,
`:minor`, `:incremental`, and `:qualifier` keys, just as
`*clojure-version*` does."
[msg]
{:cider-version version})
(in-ns 'boot.user)
(require 'nrepl.server)
(defn nrepl-handler []
(require 'cider.nrepl)
(ns-resolve 'cider.nrepl 'cider-nrepl-handler))
(nrepl.server/start-server :port 7888 :handler (nrepl-handler))
(require '[java-interop.core :refer [jvm]])
;; NOTE: Code completion works in rebel-readline,
;; but it limits how many completions are shown at once
;; Try CIDER (you have an nrepl instance running now localhost:7888)
;; Eldoc also works in cider
;;
;; example
(jvm
[(Integer:parseInt "12") (String:charAt "test-string" 0)])
You should now have an nrepl server running on localhost:7888 which you can connect to from CIDER. You can try the completion (though not documentation) right in rebel-readline's repl.
So give it a try.... you'll notice static fields aren't handled perfectly (easy fix, but again I really am looking for feedback on the concept, and am wondering who would use it etc).
(jvm (Integer:MAX_VALUE))
I think the code completion + eldoc in CIDER is a productivity boost for sure.
--
You received this message because you are subscribed to the Google Groups "Clojure" group.
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to ***@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+***@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Loading...