Want this course?
Creating a Clojure Library for use in Java
Course: JVM Fundamentals for Clojure
Have you ever wanted to solve a problem with your Java app by using Clojure? Have you ever wanted to "sneak in" a dependency on the Clojure JAR but didn't know what to do with it once it was there? This lesson shows you how to create a Clojure library that you can use from Java.
So let's say that you're working on a Java project. Part of your job is that you're on a team, and you just know that some problem that you're trying to solve would be much easier to do in Closure, but you don't know how to include the Closure code in your job, you don't want to include Closure code directly in your project, but if it were part of a library, it would just be a jar include, and that's something that you feel confident doing. So you want to solve the problem in Closure and include it in a Java project, how do you do that?
Well I have a Java project here, I've written it all out, and it has this sticky problem which is that I'm using a lot of threads, so if you look over here, this thing is called a NumberGenerator, it extends Thread as the run method, and it does a bunch of stuff in a loop, and that generates random numbers and then it calls this average method, and what that's doing is it's aggregating the numbers into an average, and you notice it's red, that's because it can't resolve this NumberCruncher class. Okay and the NumberCruncher is being passed in as a parameter to the constructor. So what I would like to do, because its using a lot of threads, it's a concurrency thing, Closure is good at that, I want to write the number cruncher class in Closure and have it be a library that I pull in. So let's look a little bit deeper at what this is doing.
So I need the constructor that takes zero arguments, it needs to, as you see here, be an iterable, so NC is my NumberCruncher, it needs to implement Iterable, and then the final thing is, it needs to have NC, NumberCruncher needs to have an average method that takes a number, okay? So let's go into Closure and make this happen. Alright so I've got some code that I've written in Closure, and this is a very simple example so I've just written it. Basically it's just a regular Closure namespace, number-cruncher.core, uses core async, it's going to have a function called average-in which takes an average which is a tuple of a numerator and a denominator, because an average is a ratio, and it's going to add x to the numerator and increment the denominator, so it's counting how many things it put in and it's summing up all the things put in.
Now this value, this tuple is being put in an atom and that atom is a, and so I'm just gonna swap, average in on that atom with x, so as this function we'll just get the current value of an atom, average in a new number and then of course swap overturn the new average. And then I divide them out, turn it into a double and put on the channel, so every time I average in a new number, the current average goes in on this channel. That seems simple enough.
Now finally, I have a thing that will turn a channel into a seq. This blocks on the channel until a value comes in. If it's nil, that means the channel is closed to that the seq should be done, so I'll just return nil. Otherwise it's gonna concept v with a recursive call. And this is doing it all lazily, so it's not trying to take from the channel all the time, only when you take will you block. And this is blocking take so the seq will block.
Okay so I have all these parts that work in Closure, this is all Closure code, but I wanna call it from Java. How do I do that? Well it's actually quite, quite, it's a bit of work getting all the puzzle pieces put into place but we'll do it one step at a time. Alright the first thing you're gonna need is a gen-class, and what this does is it makes sure that this will generate a class file that is readable from Java. It's not just a code that's gonna be compiled on the fly, it's gonna be a class file, and you can give it a name.
So this one I'm gonna call com.lispcast, and that's the package name, it's gotta be fully qualified, so I'm calling com.lispcast.NumberCruncher which if you remember is exactly the class, package com.lispcast and NumberCruncher is the name of the class, so that's perfect. Now, I'm just gonna give you, there's a lot of options to gen-class, I'm just gonna give you the ones that are necessary for this particular problem but there is a lesson, a module on all the options of gen-class. Okay so you need to give it a name, this is a symbol, state, it's the name of the field on the object, on the class that you're gonna store your state in, you can call it whatever you want but I'm calling mine state.
This is the name of a function in this namespace that will be used to initialize the state. You can give it a list of interfaces that the class can implement, and so this one I'm doing iterable because we already saw that we need to do iterable. And then you give it a list of methods that you need to give the signature for. You don't need to give the ones that are automatically done from the super class, so, Iterable has the .iterator method and you don't need to specify it's type because it's already implied in the interface. So average takes a number, just one argument, it's a list, but it's only got one thing in it, one type, and it's void. So it doesn't return anything.
Okay, so that's the gen-class that we'll need. I'm just gonna scroll to the bottom and start working on each of these parts, so I want to work on init. Init is the name of the method, but closer, the compiler of Closure is going to be adding in a prefix. The default prefix is just a hyphen. You might have seen before something like this, oops, right, that's the default prefix is the hyphen.
So I'm adding it to init, prefix then init, because I named an init here, so this has to match that. You can set the prefix to whatever you want, I'm just gonna use the default. Alright so init, whatever you use for init has to return two things, the first one is the list of arguments to the super constructor. Because the super constructor in this case is gonna be object, super class is object because we haven't given it a super class, we just need an empty list. Second thing is the state, and I'm gonna use a map so I can put multiple things in and refer to them by name. So I'm going to initialize an atom to 00, that's like the identity average, the start here, so that will be 00, and I'll use a channel. And this channel, I want it to have sliding buffer, which means it's gonna drop the older averages, so as new averages come in, they might push the older averages off and that's sliding buffer. And it'll have 100 spots in it, not really important how many.
Okay, now I need to define my average method, notice this thing is matching this up here, but it also has a prefix, and if that matches this method here, okay? So it's all matching, you gotta make sure it all matches. And of course it needs this, all the methods will start with this, and then the number that it's averaging in. So what does it do? Well first I wanna get my state, so I need atom and chan, and I'll get that from state this, remember we defined this up here, the hyphen means it's a field, so .hyphen, just gonna get the value from that field. And this is obviously the current instance. And we're just gonna call average the one that defined above with the atom, the chan and x. And of course we just won't return nil so we'll just do that.
Okay, now we have to define iterator because we want to define, we want to implement iterable, so I already have a way of making a seq, chan, so I can do it like that. But I didn't turn that into an iterator, so, Closure.langlseqIterator. And there we go. Alright so now all the code is ready but I need to do one more thing, this is the project file, notice that I've given the project, also a group ID, so maybe the group ID and the artifact ID. So I've given it the group ID of com.lispcast. And I need to add one more thing.
So I need to aot compile this, this means don't load the code at runtime and compile it means, it means compile it now, generate a class, save it to the class file, so we need to tell it which name spaces to compile that way. Okay and so now I should be able to go to my terminal in that directory and do lein compile. And this will compile everything that it needs to, you know it figures it out, and now if I notice, I have a target directory which is where all the compile stuff, tree, target, now it compiles a lot more than just your code. But notice it did compile my code and it made the class that I want, the com, lispcast, NumberCruncher class. Okay, now I'm gonna do lein, so I can do lein jar, and this is gonna generate the jar file, I can take that jar file and drag it into intelliJ.
A lot of people still do their dependencies that way, the problem is you won't get all the transitive dependencies you won't get Closure itself, you won't get core async, everything else that those two depend on, you won't get those because you're not, you know, you have to do that manually. Another thing I can do is do a lein install. And what this does is it builds the jar and puts it in a local maven repo that's in my home directory. Now I can go into intelliJ, go into the pom, notice it's still missing, that's why these are underlined, still got an error, and I'm gonna hit Command + N in intelliJ, Add a Dependency, and I can do com.lispcast, and, there's, 1.1 snapshot, there, okay, so it added the dependency and look, it added all of the transitive dependencies, it added core async, et cetera.
Okay, now, I'm working for your old directory that's great but now if you want to deploy it it doesn't work anymore. So, what you do in that case, I deleted it, it should have deleted it over here, in that case you can actually deploy this to, if your company has a Maven Repo you can put that jar and the pom file right in there. The pom file is easy to generate, just do lein pom and it will generate a pom file. You can also put it in clojars, which is what I'll do right now. So lein deploy clojars, it's gonna ask me for my username, and my password. Compiling again, it's gonna push everything up. It's very small so it'll be fast. Oh, I must have mistyped my password.
Okay, and it's uploading, there, alright so now, back to intelliJ, thing is we need to say in our pom where clojars is, because probably you don't have clojars as a repo. So to do that, I'm gonna cheat a little, I'm gonna open the pom file and notice in the pom that it does have clojars listed, this is the pom file that was generated by Lenny for this project. So I'm gonna copy that and just paste it right in. But I'm gonna get rid of that Maven central, there should already be Maven central in there, so I don't want redundant now, I should be able to add a dependency. Com.lispcast, number cruncher, and you'll notice there's the 1.1, there's 1.2, some older ones, some other ones that I've done, these are all up on clojars.
I'm just gonna add 1.1, there we go. And notice the transitive dependencies have come back, I can run this again. Did I run it the first time? Well there you go, this is what it's supposed to be doing. Printing out, this here, it's printing out everything from this interval, so it's getting values from the atom and printing them out. Okay, so that's how you include Closure code in Java.