Skip to content

Instantly share code, notes, and snippets.

@JulienRouse
Last active September 4, 2022 04:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JulienRouse/8ed8693b352318cbfaa59b76c0f4d3cb to your computer and use it in GitHub Desktop.
Save JulienRouse/8ed8693b352318cbfaa59b76c0f4d3cb to your computer and use it in GitHub Desktop.
Notes about clojure-java interop for a Montreal Clojure Meetup lightning talk
;Montreal Clojure Meetup 2019
;;sources
; - https://clojure.org/reference/java_interop
; - https://leanpub.com/clojure-java-interop/read_sample
; - http://blog.jayfields.com/2011/12/clojure-java-interop.html
; - https://stackoverflow.com/questions/2181774/calling-clojure-from-java
; - https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2-200
;;Class Access
;All classes in java.lang are automatically imported to every namespace
String
;-> java.lang.String
(defn date? [d] (instance? java.util.Date d))
;-> #'user/date?
(.getEnclosingClass java.util.Map$Entry)
;-> java.util.Map
;;Member Access
;Preferred ways of accessing member and methods
(.toUpperCase "fred") ;Method call on instance
;-> "FRED"
(.getName String) ;Method call on Class
;-> "java.lang.String"
(.-x (java.awt.Point. 1 2)) ;Field instance
;-> 1
(System/getProperty "java.vm.version") ;Static method call
;-> "1.6.0_07-b06-57"
Math/PI ;Static field access
;-> 3.141592653589793
;;Dot Special Form
;behind the scene, they are expanded to the dot special form
(.toUpperCase "fred") => (. "fred" toUpperCase)
(.getName String) => (. (identity String) getName)
(.-x (java.awt.Point. 1 2)) => (. (java.awt.Point. 1 2) -x)
(System/getProperty "java.vm.version") => (. System getProperty "java.vm.version")
Math/PI => (. Math PI)
;dot dot
;You can write this
(.. System (getProperties) (get "os.name"))
;which is equivalent to
(. (. System (getProperties)) (get "os.name"))
;also
(-> (System/getProperties) (.get "os.name"))
;doto
(doto (new java.util.HashMap)
(.put "a" 1)
(.put "b" 2))
;constructor
(String. "Hello") => (new String "Hello")
;;Support for Java in Clojure Library Functions
;Lots of goodies like:
; - function to work with Java arrays
; - let/loop/recur able to work with unboxed primitive
; - unchecked operation for when you want extra speed
; - many more!
; see https://clojure.org/reference/java_interop#primitives
;;Implementing Interfaces and Extending Classes
; proxy -> extend classes and/or interfaces
; reify -> to implement interfaces
; gen-class -> generate statically-named classes and .class files
; see https://clojure.org/reference/java_interop#_implementing_interfaces_and_extending_classes
;;Type Hints and Reflection
;Clojures runtime relies on types a lot.
;When it knows what type an object belongs to, it doesn’t spend extra time on reflection and thus performs faster.
;You can help the compiler to guess types by adding hints.
;A type hint takes its place in front of a variable in function signatures or let bindings.
(defn stop-process
[^Process p]
(when (.isAlive p)
(.destroy p)))
; Use this to help see when you can help the compiler
(set! *warn-on-reflection* true)
;Sometimes you need to express complex type to clojure, you may do it with a string like
(defn ^"[Ljava.lang.String;"
args->command
[args]
(into-array String (map str args)))
;;Coercions
;At times it is necessary to have a value of a particular primitive type.
;These coercion functions yield a value of the indicated type as long as such a coercion is possible:
; - bigdec
; - bigint
; - boolean
; - byte
; - char
; - double
; - float
; - int
; - long
; - num
; - short
;;Some optimization tips
;All arguments are passed to Clojure fns as objects, so there’s no point to putting arbitrary
;primitive type hints on fn args (excepting primitive array type hints, and long and double).
;Instead, use the let technique shown below to place args in primitive locals if they need to participate in
;primitive arithmetic in the body.
;example
(defn foo [n]
(loop [i 0]
(if (< i n)
(recur (inc i))
i)))
(time (foo 100000))
;"Elapsed time: 0.391 msecs"
;100000
(defn foo2 [n]
(let [n (int n)]
(loop [i (int 0)]
(if (< i n)
(recur (inc i))
i))))
(time (foo2 100000))
;"Elapsed time: 0.084 msecs"
;100000
;; A bit unrelated but this gist =>  https://gist.github.com/ericnormand/b29ef113401ad2a6f656c4b701fb08a7#file-valentin-waeselynck-clj
;; show many implementations of the Sieves of Eratosthenes, with many implementation trying to get realy speedy, and some use some Java interop
;; like bitSet, boolean-array (and also transient vector which is not interop but very cool too!)
;;
;; And this link is the reflexion of some Clojurian trying to collaborate to solve the problem https://clojureverse.org/t/eratosthenes-party-time-a-k-a-feedback-wanted-on-this-implementation-of-eratosthenes-sieve/3801/12
(ns awtcolor)
(set! *warn-on-reflection* true)
;;Static Colors
(def black "The color black." java.awt.Color/black)
;; Example of wrapping overloaded Java methods
; One solution is to dispatch based on the type of parameters, but is probably not what you want
(defn construct-color-v1
([rgb] ;<- you use the bits of an int to determine the color, does not display well
(java.awt.Color. rgb))
([rgba has-alpha]
(java.awt.Color. rgba has-alpha))
([r g b]
(cond
(instance? java.awt.color.ColorSpace r)
(let [cspace r
components g
alpha b]
(java.awt.Color. ^java.awt.color.ColorSpace cspace (float-array components) (float alpha)))
(float? r)
(java.awt.Color. ^double r ^double g ^double b)
:default
(java.awt.Color. ^long r ^long g ^long b)))
([r g b a]
(if (double? r)
(java.awt.Color. ^double r ^double g ^double b ^double a)
(java.awt.Color. ^long r ^long g ^long b ^long a))))
;Another take on this is to create many clojure function to wrap the overloaded method
(defn construct-color-with-float
([r g b]
(java.awt.Color. (float r) (float g) (float b)))
([r g b a]
(java.awt.Color. (float r) (float g) (float b) (float a))))
(defn construct-color-with-int
([r g b]
(java.awt.Color. (int r) (int g) (int b)))
([r g b a]
(java.awt.Color. (int r) (int g) (int b) (int a))))
(defn construct-color-with-color-space
[^java.awt.color.ColorSpace cspace components alpha]
(java.awt.Color. cspace (float-array components) (float alpha)))
;; Dealing with code that expect a Java array
;One parameter of the Java getColorComponents method is `comp-array`.
;`comp-array` must be a Java array of float, sp you need to wrap every sequence you pass to the method with float-array
;Note that (float-array (float-array [1 2 3])) is the same as (float-array [1 2 3]) so you don't have to
;worry about user wrapping themselves (or not) their sequences.
;Also use `vec` or `into` to transform a Java array back into the Clojure world
(defn get-color-components
([^java.awt.Color color]
(vec (.getColorComponents color nil)))
([^java.awt.Color color comp-array]
(vec (.getColorComponents color (float-array comp-array))));<- here is float-array
([^java.awt.Color color ^java.awt.color.ColorSpace cspace]
(vec (.getColorComponents color cspace))))
; Not also that in this case, if you provide null instead of an array, it works too
@JulienRouse
Copy link
Author

Zach Tellman - elements of clojure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment