My mom cooked the same food every day - tortillas, beans and meat. If it was enchiladas, it was - tortillas, beans and meat. If it was burritos, it was still - tortillas, beans and meat.
— Felipe Esparza
Interfacing to Java libraries from Clojure can be ugly. You have to use the special interop syntax, and either sprinkle type hints all over the place, or risk having runtime reflection killing performance.
Calling vararg methods is also clumsy, as normal Java interop in Clojure
doesn't have any special handling for them, so you have to manually wrap
the variable arguments in a Java array using e.g. into-array
.
Java functions also tend to expect different types which need special handling to convert the usual Clojure values into the exoected types.
For example functions that expect an Integer
or a Float
need the argument to be wrapped in (int x)
or (float y)
, those that expect an array need a Clojure vector or list to be wrapped in (into-array TYPE my_vec)
.
Additionally, in Java 8 there are Funcional Interfaces (that accept a lambda expression in Java), but from Clojure you need to wrap the Clojure function in proxy
or reify
to convert it to the right type.
Tortilla aims to remedy this by using reflection at compile time to
automatically generate reflection-free idiomatic Clojure function wrappers
around Java class methods.
These wrappers then know the parameter types of the various candidate overloads and can select the correct one with simple type checks that are much faster than full runtime reflection.
They provide variable-arity functions to wrap vararg methods so that
they can be called idiomatically from Clojure.
Tortilla also includes support for automatically coercing Clojure types.
So, for example, you can just pass normal Clojure longs and doubles, and tortilla will coerce them to Integer and Float respectively, when necessary.
You can also pass in a Clojure vector when an array is excepcted, or a Clojure function when a Functional Interface is expected (e.g. java.lang.function.Function
, java.io.FileFilter
...)
Tortilla is still in the early alpha stages of development and has not yet been heavily tested, so it is not reecommended for use in production code. However, the basic functionality is mostly in place, so feel free to give it a try, and provide feedback if you find anything that doeesn't work, or any functionality that's missing.
There are two ways to use Tortilla: either as a macro that you call in your code to generate the wrapper functions at compile time, or as a command-line interface (CLI) that you run as a separate step to generate Clojure source files that you then build with the rest of your code. There are pros and cons to both approaches:
:refer-clojure :exclude
clause to the ns
form to avoid compiler warrnings about redefined symbols.Add Tortilla as a dependency to your project. The latest version is:
You define wrappers for a Java class by calling the defwrapper
macro, for example:
;; define the wrapper functions:
(defwrapper java.io.File)
;; create a File object
(def f (clojure.java.io/file "project.clj"))
;; call the generated functions on the File object
(get-name f) ;; => "project.clj"
(is-directory f) ;; => false
(exists f) ;; => true
(length f) ;; => 2996
(last-modified f) ;; => 1602755163000
In the above example, Clojure will emit a warning like:
WARNING: list already refers to: #'clojure.core/list in namespace: user, being replaced by: #'user/list
This is because the File class has a member function called list
, so when the wrapper function is created, this clashes with the list
function in Clojure core.
There are two ways to work around this:
Either you can exclude the Clojure core functions from being imported by adding a clause to your ns
form like:
(:refer-clojure :exclude [list])
If you use the CLI mode (see below), this can be handled automatically. If you still need to refer to the excluded function, you can still access it using its fully-qualified name (clojure.core/list
).
Alternatively, you can add a prefix to all generated function names when generating the wrappers:
(defwrapper java.io.File {:prefix "f-"})
(f-get-name f) ;; => "project.clj"
Note that it's still possible to get clashes with a prefix, for example if a method is called cat
and you use a prefix of lazy-
, the generated function will clash with Clojure's lazy-cat
.
Tortilla can handle the automatic coercion of values to Java types. By default it will coerce:
(f 1)
instead of manually coercing like (f (int 1))
.(f 1.0)
instead of (f (float 1.0))
.(:import [package.name EnumClass])
in your ns declaration then use (f EnumClass/VALUE)
, you can just use (f :VALUE)
.(f ["Hello" "world"])
instead of (f (into-arrray String ["Hello" "world"]))
.(list file #(str/ends-with? %2 ".txt"))
instead of having to do something like:
(let [filter (reify java.io.FilenameFilter
(accept [_self _dir name]
(str/ends-with? name ".txt")))]
(list file filter))
Note that tortilla currently by default only supports a predetermined list of Functional Interfaces (listed in tortilla.coerce
).
Support for other types can be added by calling the tortilla.coerce/coerce-fn-impl
macro with the new Functional Interface.
The default coerce implementation can be extended by extending the Coercible
protocol in tortilla.coerce
to new Clojure types and/or defining new methods for the coerce-long
, coerce-double
, coerce-kw
, coerce-vector
and coerce-fn
multimethods.
Alternatively, a completely different implementation can be passsed in to defwrapper
(or the keyword :none
to disable coercion). The new function should accept two arguments: the clojure value and a Class
object representing the target type. It should return either a value of the target type if it can coerce successfully, or the original value unaltered.
You can download the latest CLI executable from github,
or build it from source with lein bin
in a clone of this repository, which will put the executable in the bin
subdirectory.
You can get an overview of the options available with the --help
/-h
option:
> ./bin/tortilla -h
Usage: tortilla [options]
Options:
-c, --class CLASS Class to generate a wrapper. May be specified multiple times.
-m, --members Print list of class members instead of wrapper code (useful for checking -i/-x).
-i, --include REGEX Only wrap members that match REGEX. Match members in format name(arg1.type,arg2.type):return.type
-x, --exclude REGEX Exclude members that match REGEX from wrapping.
-n, --namespace NAMESPACE Generate ns form at start of output with given name.
--[no-]refer-clojure Generate refer-clojure clause excluding any wrapped names.
--coerce SYMBOL Use SYMBOL for coercion (or 'none' to disable, empty for default).
-p, --prefix PREFIX Prefix generated function names (useful to avoid conflicts with clojure.core names.)
-o, --out FILE Write generated output to FILE.
--[no-]metadata Include metadata in output.
--[no-]instrument Instrument specs.
--[no-]unwrap-do Unwrap 'do' form around defns.
-w, --width CHARS 100 Limit output width.
-d, --dep COORD Add jars to classpath. May be specified multiple times. COORD may be in leiningen format ('[group/artifact "version"]') or maven format (group:artifact:version). In both cases the group part is optional, and defaults to the artifact ID.
-v, --version Display version information.
-h, --help Display this help.
Here is an example of using the CLI to generate a wrapper file for the File class:
# Generate wrapper file:
> ./bin/tortilla --class java.io.File --namespace java.io.file --out src/java/io/file.clj
# Look at the start of the generated file:
> head -n 10 src/java/io/file.clj
(ns java.io.file
(:refer-clojure :exclude [list])
(:require [tortilla.wrap]))
;; ==== java.io.File ====
(clojure.core/defn can-execute
{:arglists '([java.io.File])}
(^{:tag java.lang.Boolean}
[p0_271]
Tortilla is based on a post by Arne Brasseur on ClojureVerse. Since the code was only available in a gist, I took the liberty of cloning it into a full repo and developing it further.
Copyright © 2019 Arne Brasseur and Emlyn Corrin
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close