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.

Reply via email to