Clojure-FastR-interop
This notebook studies the possibility of using GraalVM's FastR from Clojure.
Previous work used for inspiration:
- Using polyglot GraalVM from Clojure by Carin Meier
- Setting up FastR in GraalVM by Martin Kavalar
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"}}}
(use clojure.tools.deps.alpha.repl) (clojure-version) (add-lib com.rpl/specter {:mvn/version "1.1.2"})
apt-get update apt-get install -y libomp-dev gcc make
gu install org.graalvm.R
Playing with interop
(set! *warn-on-reflection* 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))
(-> Context context (.eval "R" "1+2") (.asDouble))
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)))))
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)))
(defn proxy-object [m] (-> m (ProxyObject/fromMap)))
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))
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))
Let us try it:
(r-mean [10 20 30 40])