Skip to end of metadata
Go to start of metadata

Problem Statement

  • People looking to call Clojure code from Java are faced with a large number of Java classes from the Clojure implementation and don't know which ones are stable and safe to use.

Other problems, not in scope

  • people don't like to type
    • why are they using Java then?
  • people don't understand Clojure, but still want to leverage it from Java
    • Clojure is not a Java library
    • People can and do write libraries in Clojure for consumption from Java, but each must define its own Java API

Mission

  • Solve the problem, without introducing others

Proposal

  • provide the smallest possible API, in a single class, to allow people to find and call Clojure fns
    • a single thing to refer people to
    • opens up entire API
      • everything in Clojure is exposed via fns
      • if something critical is missing, let's add a fn rather than grow this API
    • no wrappers that need to be maintained
    • no semantic or other mismatch with original fns
    • docs are Clojure docs

Issues

  • We want to give people access to Var objects but not have them use the Var class
    • too much other stuff there, plus it's concrete
    • the interfaces they need are IFn and IDeref
    • but no interface unifies them, so we have no good type to use
      • make one?
  • bindings are also tricky to use from Java
    • can we function-ify them?
      • with-bindings (fn!)
      • with-repl-bindings
      • with-compilation-bindings

Stable Public Java API

Clojure does not currently provide a stable public Java API.  This makes calling Clojure code from Java more verbose and more complicated than it needs to be, and requires developers to refer to classes that are actually just details of Clojure's current implementation that can and probably will change in the future.

A public Java API probably only needs to be a single class providing a small set of static utility methods, primarily intended to smooth the path for calling Clojure functions from Java.  Off the top of my head, this might be an initial set of such methods:

In addition, some data-related methods would be helpful:

As long as we are careful with our naming, all of these methods could be statically imported in Java files, making a lot of common interop tasks much less verbose.

IFn.invoke throws Exception :-(

Because clojure.lang.IFn.invoke throws Exception, calling Clojure functions in Java (via IFn or resolved vars) implies a lot of checked exception pain.  Without some solution to this, the public API for Java would also bubble up that pain through any call method or .invoke calls made using Var or IFn obtained through that API.

h3. Potential solutions

Wrap IFn with a delegate that catches Exception and rethrows them as RuntimeException.  An example of this is SafeFn.java, which allows nREPL connection functions to be used from Java with a minimum of exception management pain.  The public Java API would presumably perform this wrapping automatically when making calls or when returning a function or var that will be {{.invoke}}d later.

Alternatively, drop the throws Exception declaration from IFn.invoke entirely, and change function code generation to catch Exception and rethrow them as RuntimeException.  That would simplify the Java interaction, not force the introduction of one or two new classes, and avoid any wrapping inefficiencies, but I don't yet know what the repercussions would be to the compiler to effect this change or what the perf impact would be (either at runtime or compile/load time given the additional heft of each new function class).

RH - or drop the declaration and don't wrap. Checked exceptions are a fiction of javac, and not a fiction of Clojure. Implemented.

Prior Discussion

CLJ-452

2010/03/21 clojure-dev thread

Labels:
  1. Mar 22, 2011

    Wow, I don't like this at all. deref/call taking strings, encouraging a lookup on every use? Wait for the reports that Clojure interop is slow.

    deref and call are redundant, in that IDeref and IFn have methods for those. For all of the rest, there is a Clojure fn already.

    Are we going to pile the whole Clojure API in here just so someone doesn't have to say invoke, and if not, how to explain what's there and what isn't? Are we going to document all of these similar-to-but-not the same wrappers? Are you going to lose fast variadics?

    Please limit the scope of this to:

    Finding Clojure vars, and using them to call the fns they hold.

    Upsides:

    • what functions are available?
      • all of them
    • where are the docs?
      • the Clojure docs
    • what do I have to learn?
      • how to get a fn and invoke it

    Downsides:

    • you have to obtain a fn before you call it
      • and if you call it frequently, make sure you retain it rather than refind it over and over
    • you have to call invoke()

    If you want to save people step one for some popular fns, then make them static final public IUnifyDerefAndIFn members statically initialized to the Vars.

    Making things less verbose in Java is a non-target. That's why they use IDEs :)
    Making Clojure into a Java library is also not the target.