Daniel Slutsky / Aug 17 2019
Remix of Clojure by Nextjournal

Clojure-FastR-interop

This notebook studies the possibility of using GraalVM's FastR from Clojure.

Previous work used for inspiration:

Nextjournal seems like a great platform for documenting such topics in a reproducible way! However, at the moment some R packages (e.g., dplyr) fail to install at this environment, due to insufficient memory. Thus, I will continue this experimentation in a local setup, hoping to come back to Nextjournal later through the process.

See this project for a little more detailed examples.

Setup

{:deps
 {org.clojure/clojure {:mvn/version "1.10.0"}
  org.clojure/tools.deps.alpha
  {:git/url "https://github.com/clojure/tools.deps.alpha.git"
   :sha "f6c080bd0049211021ea59e516d1785b08302515"}
  compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation

(use 'clojure.tools.deps.alpha.repl)
(clojure-version)

(add-lib 'com.rpl/specter {:mvn/version "1.1.2"})
true

apt-get update
apt-get install -y libomp-dev gcc make
gu install org.graalvm.R

Playing with interop

(set! *warn-on-reflection* true)
true
(ns scratch
  (:import (org.graalvm.polyglot Context Context$Builder Source Value)
           (org.graalvm.polyglot.proxy ProxyArray ProxyObject)))
(def context-builder
  (doto ^Context$Builder (Context/newBuilder (into-array String ["R" "js"]))
     (.allowAllAccess true)
     (.allowNativeAccess true)))

(def context (.build ^Context$Builder context-builder))
scratch/context
(-> ^Context context
  (.eval "R" "1+2")
  (.asDouble))
3

Given some R code that defines a function, define a corresponding Clojure function. The function assumes to get Proxies as its arguments -- that is, Java objects that mimic guest language objects.

(defn r-fn [r-code]
  (let [f ^Value (.eval ^Context context "R" r-code)]
    (assert (.canExecute f))
    (fn [& proxy-args]
      (.execute f (into-array proxy-args)))))
scratch/r-fn

Given a sequential Clojure structure, one can create a corresponding ProxyArray, that can be passed as an arguments to functions of the above kind, and mimic R arrays.

(defn proxy-array [xs]
  (-> xs
      into-array 
      (ProxyArray/fromArray)))
scratch/proxy-array
(defn proxy-object [m]
  (-> m
      (ProxyObject/fromMap)))
scratch/proxy-object

Given return values of R evaluation, we can ask to see them as Java Double values (if possible):

(defn as-double [^Value v]
  (.asDouble v))
scratch/as-double

Composing the above, we can implement the computation of the mean using an R function:

(def r-mean
  (comp as-double
        (r-fn "function(x) mean(as.numeric(x))")
        proxy-array))     
scratch/r-mean

Let us try it:

(r-mean [10 20 30 40])
25