2

I found the amazing question How can I use clojure as scripting language for a Java program? which helped tremendously, but I can't figure out how to get an existing Java instance into Clojure. The use case is something really similar to AutoCad's AutoLisp. I want to let users manipulate an application with scripting so that they are free to do more without my help or input. I want to have a class that does some work

public class Testing {
    public void work() {
        // ....
    }
}

and then add it to Clojure

public class Main {
    public static void main() {
        Testing t = new Testing()
        IFn eval = Clojure.var("clojure.core", "eval");
        System.out.println(eval.invoke(Clojure.read("(import Testing)")));
        // How do i get "t" into clojure?
        System.out.println(eval.invoke(Clojure.read("(.work t)")));
    }
}

However I can't figure out how. I don't seem to be able to invoke def with arguments from java. I have been fiddling with this and with documentation for a while and can't seem to figure it out.

David Stocking
  • 1,200
  • 15
  • 26
  • Your example isn't really clear enough. If you want to call `t.work()`, you can just call `t.work()` from Java, without involving Clojure at all. What is a more realistic use-case you have in mind, where the ability to involve Clojure matters? – amalloy Jan 24 '22 at 18:04
  • @amalloy The use case is something really similar to AutoCad's AutoLisp. I want to let users manipulate an application with scripting so that they are free to do more without my help or input. Create, edit, delete in bulk. Randomly generate stuff. Create there own helper functions etc. – David Stocking Jan 24 '22 at 18:21

1 Answers1

4
import clojure.java.api.Clojure;
import clojure.lang.Var;
import clojure.lang.RT;
import clojure.lang.Compiler;


public class Main {
    public static void main(String[] _argv) {
        // Using String instead of Testing just to avoid having to
        // deal with multiple files during compilation.
        String s = "Hello there";

        // Needed to allow creating new namespaces.
        // If you ever get stuck with some functionality not working, check out
        // Compiler.load - there are other bindings in there which, I guess, might be important.
        // So you can either copy all the bindings here or simply use Compiler.load instead of
        // Compiler.eval for script pieces that don't require bindRoot.
        Var.pushThreadBindings(RT.mapUniqueKeys(RT.CURRENT_NS, RT.CURRENT_NS.deref()));
        try {
            Compiler.eval(Clojure.read("(ns user)"));
            // def returns the var itself.
            ((Var) Compiler.eval(Clojure.read("(def s)"))).bindRoot(s);

            Compiler.eval(Clojure.read("(println s \"in\" (ns-name *ns*)))"));
        } finally {
            Var.popThreadBindings();
        }
    }
}
Eugene Pakhomov
  • 9,309
  • 3
  • 27
  • 53
  • OMG this is amazing and so cool lol. Thank you so much. I didn't realize I could just bind a symbol. I was trying to do `println("Result: ${def.invoke(Symbol.intern("t"), t)}")` at one point but figured that wouldn't work because def is a macro. Anyway thanks for this! – David Stocking Jan 24 '22 at 18:33
  • Note that I just tested this and apparently the default `*ns*` in this case is not `user` but `clojure.core`. So be aware - you would be changing the core namespace. It would be better to create the `user` namespace and set it to current, but I'm not sure how to do it. – Eugene Pakhomov Jan 24 '22 at 18:35
  • I was also trying to figure out how to best put everything in namespaces, but I haven't gotten very far with that either. If you figure out more about, let me know or edit the answer. I would love to know. – David Stocking Jan 24 '22 at 18:38
  • @DavidStocking I've updated the answer, should work now. – Eugene Pakhomov Jan 24 '22 at 19:46
  • Also, check out this long discussion - there are some useful (but potentially outdated) things in there: https://groups.google.com/g/clojure/c/2YztuGDxajQ – Eugene Pakhomov Jan 24 '22 at 19:47