Comments inline... ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Tuesday, October 16, 2018 3:47 PM, 'Tatu Tarvainen' via Clojure <clojure@googlegroups.com> wrote:
> 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). > 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. > 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) > perjantai 12. lokakuuta 2018 3.22.37 UTC+3 somewhat-functional-programmer > kirjoitti: > >> 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 clojure@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+unsubscr...@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+unsubscr...@googlegroups.com. > 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 clojure@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+unsubscr...@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+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.