Saturday, August 14, 2010

ClojureCLR

Installation (Getting to the REPL)



First off, you need Visual Studio. I’m doing this with 2008, but I’m pretty sure it would also work on 2010 and the relevant Express versions. I have doubts as to how well it would work with 2005 and am clearly not going to test it.

Download ClojureCLR 1.1.0 from the github page.

Follow the instructions here.


If you have trouble with that, use these instructions. They worked for me:

  1. Download ClojureCLR and unzip it somewhere

  2. Download the Dynamic Language Runtime: http://dlr.codeplex.com/

  3. Copy the unzipped folder into your Clojure parent directory if you want the Clojure solution to work out of the box. Rename it DLR_Main. Just to make this step clear, if your Clojure solution file is C:\dev\gukjoon-clojure\Clojure\ClojureCLR.sln, then you need to have DLR in C:\Dev\DLR_Main. The subfolders for that should be C:\Dev\DLR_Main\src and C:\Dev\DLR_Main\Samples.

  4. Open the Clojure solution in the Clojure folder.

  5. For now, unload Clojure.Test from the solution.

  6. I had trouble with the post-build in Clojure.Main so I just took that out. If you do this, you will also have to copy the “clojure” folder from Clojure.Source into the output directory (“bin/Debug”) of Clojure.Main.

  7. Run Clojure!


Making a package to build external solutions


Installation is kind of a bitch with ClojureCLR, especially compared to Java Clojure. To help smooth over acceptance, I created a Clojure package that had all the Clojure binaries in it and two batch scripts that would use nant to build from target solutions and start a REPL with the target solutions loaded.

First copy your bin/Debug directory into a folder somewhere. Then, get nant and put that in the same directory.

Bootstrap.build
You’ll have to customize this nant build file for your solutions. You set a base directory for your solution (project.dir) and then can set up targets for each solution you have. I’m not very good with Nant so if you have recommendations for improving this buildfile, definitely let me know.

BuildClojure.bat
BuildClojure.bat runs the nant build file. It takes in an argument that it passes along to the nant build as the target. So depending on how you set up the nant build file, you could build specific solutions or all of them.

RunClojure.bat
RunClojure.bat is kind of worthless. It’s just one line: .\Clojure.Main.exe .\Startup.clj. I mostly have it because the Windows command prompt is a usability nightmare. I would much rather click to run Clojure.

Startup.clj
RunClojure.bat starts clojure with a startup script that loads all the assemblies you need. .NET classloading doesn’t work quite as well as Java so here I am loading the assemblies manually. You will want to change “Adc.*.dll” to something else, unless you also happen to work at AOL Advertising.

Generics


A big problem I came across was the lack of generics support in vanilla ClojureCLR. You actually need to pass in parameter types in .NET for generic types and methods and our legacy code was littered with generic types and methods. I didn’t really even need to create generics, just use them.

It’s fairly easy to hack your way around this using reflection. However, I would advise against doing it the way I did:

Clojure/CljCompiler/GenGeneric.cs
Clojure/CljCompiler/GenGenericMethod.cs
core-clr.clj additions

I created two new classes to do the actual reflection, when you can totally do the whole thing in Clojure with interop. You probably should also put your clojure code in a separate file from the other core-clr functions. This is left as an exercise to the reader.

If you choose to use this code, just add it into your solution in the appropriate places and recompile Clojure.Main and Clojure.Source (make sure to copy over clore-clr.clj.dll from the clojure directory in the Clojure.Source build.) gen-generic takes in a generic type, a vector of types and then arguments to the constructor. It will return an instance of the type. gen-generic-method takes in your callsite (either a type or an object,) the method as a String, a vector of types and arguments to the method.

Since you still need to pass in a generic type to the gen-generic function, you will have to use Type/getType to get the generic type from its string representation. In .NET the number of type parameters is reflected by a ` and then the number. For example, a list would be “System.Collections.Generic.List`1” and a dictionary would be “System.Collections.Generic.Dictionary`2.”

How I used it:


The problem I found with debugging any sort of object oriented code is that your control tends to be limited to an external interface. While the Visual Studio debugger is very helpful in narrowing down where your code is broken, the stubby fingers that C# (and Java, too) force on you make pinpointing irregular behavior harder than it needs to be. I believe that as a developer, no component of your system should be a black box if you don’t want it to be.

My debugging usually works this way:
1) Duplicate the bug in my dev environment, as described by QA
2) Walk through the callstack to pinpoint the exact point where irregular behavior occurs
3) Consistently duplicate this irregular behavior
4) Figure out how to fix it.

Any seasoned developer will tell you that 1-3 are far more difficult and time consuming than 4. Fixing shit is easy. Figuring out what to fix is hard. Furthermore, straight up exceptions tend to be easy to pinpoint using the Visual Studio debugger. The hard bugs to fix are ones that cause bad behavior without any overt exceptions.

ClojureCLR is a tool for helping with 2-3 (sorry, you’re still shit out of luck for non-deterministic bugs that are impossible to duplicate) in finding “soft” bugs. You want to stand up individual components, test these components with inputs and see if you get the expected outputs. It’s far easier to do this on the fly in Clojure than writing a seperate program, compiling it and doing it.


Anyways, long story short: don’t use ClojureCLR unless you have to, but it is useful if you have to deal with .NET.

No comments: