Comments inline...
‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Tuesday, October 16, 2018 3:47 PM, 'Tatu Tarvainen' via Clojure
<[email protected]> 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 [email protected]
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> [email protected]
> 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 [email protected].
> 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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.