Wednesday, November 19, 2008

A C# REPL (in Clojure)

It’s no secret that I’ve been interested in Lisp for quite a while. Lately, that has meant Clojure. Clojure is a new Lisp dialect that runs on top of the JVM. That means it has the power of Lisp (macros, dynamic typing, etc.) combined with the power of the JVM. The synergy reminds me of an old bumper sticker I saw years ago: “C: combining the speed of assembly with the power of assembly.” We’ve moved on a bit since then, but still. :)

Anyway, you should check out Clojure. It is easily the most exciting technology I’ve seen in years…probably since I first saw .NET in June of 2000. Even beyond just being a Lisp that actually has vast libraries (one of the main complaints against Lisps of the past) is the fact that there are some truly brilliant features of the language above and beyond what other Lisps sport.

One of the cool things I’d heard was that Clojure will run on top of IKVM.NET, which is a .NET implementation of the JVM. I figured that if there were a reasonable story for .NET interop, I’d be able to use Clojure to drive .NET code in a REPL. A REPL is a Read-Eval-Print Loop, which is a fancy way of saying “an interactive programming command line”. It’s like the immediate window in the Visual Studio debugger on steroids, and its absence is one of the increasing number of things that makes C# painful to use as I gain proficiency in more advanced languages.

Yes, I said it: C# is not an advanced language. If you think it is, then in my opinion you don’t know enough programming languages yet. :)

Intentionally provocative statements aside, here’s how you do it:

  1. Download and unzip ikvm.net. No installer required.
  2. Download Clojure. I recommend grabbing the head of the Subversion tree rather than the release that’s on the website.
  3. Build Clojure by running “ant jar” in the clojure directory. You’ll have to install Ant to do this if you haven’t already.
  4. Run “ikvmstub mscorlib.dll” to create mscorlib.jar, which will create the Java wrapper classes for the stuff in mscorlib.
  5. Launch a Clojure REPL via “ikvm –cp \path\to\clojure.jar;\path\to\mscorlib.jar clojure.lang.Repl”.

Now you have a working REPL, in which all the types in mscorlib are available to be driven by Clojure, which is most definitely an advanced language. Here’s a very simple example (the results of running the commands are indicated by =>):

(import '(cli.System DateTime))
=> nil

(new DateTime)
=> #<DateTime 1/1/0001 12:00:00 AM>

(.get_Now DateTime)
=> #<DateTime 11/19/2008 2:31:02 PM>

(.ToString (new DateTime) "R")
=> "Mon, 01 Jan 0001 00:00:00 GMT"

You get the idea.

Of course this is probably only useful as an exploratory tool: if I were writing Clojure for real, I’d write it against the JVM, not against IKVM.NET. But I use the excellent SnippetCompiler all the time now, but it’s still not the quite same thing as a real REPL, so I’m excited to have a real REPL available.

And really, the reason I’m posting this is that I’m hoping it will be a sort of gateway drug for you, and that what this will really do is kick off your interest in Clojure itself. The website is a pretty good source of information, but you can also check out Stu Halloway’s Programming Clojure book. It’s available in Beta form here. I’ve been tech reviewing it, and even the early beta looks pretty good.

17 comments:

  1. By the way, you can omit the ikvmstub step for mscorlib. You would need this (or run ikvmc with the -reference option) to access classes in your own assemblies, but mscorlib is free.

    ReplyDelete
  2. Ah! Good to know. I'll probably build up a bunch of assemblies (I assume I have to do this for System, System.Core, etc.) and then just create a csrepl.cmd command file that launches everything for me.

    ReplyDelete
  3. Another option on the .NET REPL front is Mono's REPL:
    http://tirania.org/blog/archive/2008/Sep-08.html

    Only supports that primitive language C# though. ;)

    ReplyDelete
  4. Certainly not LISP, but PowerShell also works as a decent REPL for C#:

    $ new-object DateTime => Monday, January 01, 0001 12:00:00 A.M.

    $ [DateTime]::Now => Wednesday, November 19, 2008 4:14:31 P.M.

    $ (new-object DateTime).ToString("R") => Mon, 01 Jan 0001 00:00:00 GMT

    ReplyDelete
  5. So let's get this straight. An advanced language is one.. without a type system?

    ReplyDelete
  6. Can you please tell us more about why you think C# is not an advanced language?

    What or How do you do things in Clojure that you can't in C#?

    ReplyDelete
  7. If you think that's C# then in my opinion you don’t know enough programming languages yet. :)

    ReplyDelete
  8. Hah! I debated even making the comment about C# not being an advanced language, but hey: it's my blog and I happen to think it's true.

    What can you do in Clojure that you can't do in C#? Macros. Super powerful, don't exist in C#. Ditto multimethods. Ditto (convenient) dynamic binding. Ditto full-featured lambdas, although C# doesn't totally suck on that last one. These are all things that I would use in my C# work today if I had them.

    Also, dynamic typing does not equal no typing. However, it does generally equal less "typing", as in the sort you do with your fingers.

    Anyway, I'm going to stop debating about the "advancedness" of C# now. Check out Clojure and/or Haskell and/or Ruby - you can judge for yourself. I'll still respect you if you learn one or more of those and still prefer C#. :)

    ReplyDelete
  9. "What can you do in Clojure that you can't do in C#? Macros."

    Macros are great, really, and I miss them in any language without them, but it's usually better to have the feature you add via macros present in the language directly, if it's reasonable to do so. In Common Lisp, probably 80% of macros are written to make one or more parameters lazy, whereas in Scala you can do it by changing the type of the parameter from, say, "Int" to "=> Int".

    "Ditto multimethods."

    I don't believe in object-oriented programming. However, you can achieve the same results through the visitor pattern, or a Dictionary>, etc.

    "Ditto (convenient) dynamic binding."

    Dynamic binding is useful for novices and for working around type system limitations. As such it's hopefully something we don't have to use much, but anyway, it's to be added in C# 4 via the 'dynamic' keyword.

    "Ditto full-featured lambdas"

    C# lambdas can't return from the method they're in, or cause surrounding loops to break or continue, or goto surrounding labels. Are these the points that make them not full-featured?

    "Also, dynamic typing does not equal no typing."

    Lisp is a programming-language encoding of the untyped lambda calculus. If the untyped lambda calculus is typed, I'm a monkey in a tree.

    "However, it does generally equal less "typing", as in the sort you do with your fingers."

    C# isn't great at this, but does have local variable type inference and limited type parameter inference.

    "Check out Clojure and/or Haskell and/or Ruby"

    I like and use Haskell. I am competent with Common Lisp, Scheme and employed programming Java, so Clojure is probably not any different to how I would expect.

    I prefer typed languages to untyped ones, because I think in types. That said, some typed languages are better than others. I'd prefer an untyped language to a typed language without generics.

    "I'll still respect you if you learn one or more of those and still prefer C#. :)"

    I don't prefer C# to Haskell, but I prefer it to Lisp, Python etc.

    ReplyDelete
  10. "What can you do in Clojure that you can't do in C#? Macros."

    Don't forget:

    - In Lisp data and programs are equal. You can apply data to programs and vice versa with the same syntax.

    - Lisp's syntax is extensible while C# has a fixed syntax. AFAIK Perl 6 is only modern development that supports syntax extension as well.

    - Lisp functions can be recompiled at runtime. It is even possible to stop at a breakpoint, fix the problem and just continue.

    It' s amazing that the 50 year old language Common Lisp is still as powerful as (or even more than) the most modern languages today. They still learn and copy features from Lisp :-)

    ReplyDelete
  11. To be fair, this is more a CLR REPL than a C# REPL...

    ReplyDelete
  12. @Ricky: So, you've obviously done your homework, and come down on a different side of the issue than I have. I can respect that: you've made an informed value judgment. I like to think I've done the same.

    To your specific points:

    "it's usually better to have the feature you add via macros present in the language directly, if it's reasonable to do so."

    Agreed. However, the difference between a language like Lisp and one like C# is that I don't have to wait for the folks at Microsoft to introduce said language feature. C# will eventually get macros, or more probably a crippled version of them, but I'll have to wait years for that. Plus, it'll add more syntax to a language that already has a hell of a lot of it.

    "I don't believe in object-oriented programming. However, you can achieve the same results through the visitor pattern, or a Dictionary>, etc."

    Sort of. Specialization on values (e.g. null) is still a nice, expressive thing to be able to do. And generics of generics of generics is extremely awkward IMO - Clojure particularly shines here due to support of map, set, list, and array literals - an example of where the language has introduced extra syntax (over and above the extremely minimal syntax of most Lisps) where it makes practical sense to do so.

    "C# lambdas can't return from the method they're in, or cause surrounding loops to break or continue, or goto surrounding labels. Are these the points that make them not full-featured?"

    Not really - that sort of non-obvious control flow isn't something I find myself wanting very often. I guess my complaint is more with the constraints imposed by the type system rather than with lambdas per se. E.g. you can't do this:

    object o = a => Process(a);

    And that's occasionally handy.

    Note that the dynamic keyword they're adding won't help with this at all.

    "Lisp is a programming-language encoding of the untyped lambda calculus. If the untyped lambda calculus is typed, I'm a monkey in a tree."

    This one probably comes down to a definition of "typed". Lisp objects have a type - variables don't, a la the var keyword in C#, albeit without the type inference, which I find to be a hindrance more often than a help.

    "C# isn't great at this, but does have local variable type inference and limited type parameter inference."

    Right. And the "isn't great" and "limited" parts of that are where reasonable people can come down on different sides of the issue.

    "I prefer typed languages to untyped ones, because I think in types. That said, some typed languages are better than others. I'd prefer an untyped language to a typed language without generics."

    And for me, I find that types are often overhead.

    You really ought to check out Clojure: as I mentioned, it has support for map, set, list (of course) and vector literals, which sets it aside from most other Lisps and (IMO) removes the needs for most types. You don't get static type checking, of course, and that may be a deal breaker for you. It isn't for me.

    Also, Clojure is functional, objects are immutable (and compare by value), and has built-in support for STM. Those things might appeal to a Haskell guy.

    ReplyDelete
  13. @Tim: fair enough. Worse yet, I haven't yet tried to work with generics, but it's probably truly awful to do so given the type name mangling that goes on. And I have no idea what the mapping via IKVM even looks like...

    But it's still sort of handy to have this sort of thing once in a while. And @Chuck: sorry, but I've just never been able to like PowerShell. If I did, though, it would probably make a more convenient REPL than Clojure. Everyone should check it out and judge for themselves.

    ReplyDelete
  14. Weekly digest of interesting stuff

    ReplyDelete
  15. Pingback from Clojure Roundup: Post-Thanksgiving vacation edition « Clojure Study Group DC

    ReplyDelete
  16. It’s the end of the year, and although I skipped 2007, it is my habit to take a look back here on matters

    ReplyDelete
  17. Stephen Gilardi did this for me months ago for a real project with a real need to access C# libs and it worked.

    ReplyDelete