Cloture: Clojure in Common Lisp


129 points | by ruricolist 286 days ago


  • slyrus 286 days ago

    I have long desired being able to run clojure code directly in my Common Lisp environment. Interop between clojure code and CL code would be much more interesting to me than the clojure/java interop. Yes, there's ABCL which does the CL/Java interop (and of course vanilla clojure has that built in), but I'm a big fan of the idea.

    And ruricolist has written (and maintained/forked) some nice libraries. In particular his FXML is the modern successor to cxml and plays nice with xpath, etc...

  • stereolambda 285 days ago

    Maybe this could lead, in a roundabout way, to saner ways of dealing with hash tables and non-list sequences in general in Common Lisp. Currently these need clunky, verbose code for many use cases. Writing your own functions/macros is of course possible, but it feels disorganized without ensuring an overarching system of conventions. AFAIK a uniform way of handling sequences was one of the early selling points of Clojure, according to Rich Hickey himself. Sequences which are immutable, which I don't care much for (as I cherish the out-of-your-way CL philosophy), but it's a compromise.

    Alternatively I could spend some time assessing rutils or some other fundamental extension library... I'd like, one day, for one of those to gain a good tutorial and a bigger mindshare in the community.

    • bjoli 285 days ago

      Yeah. In scheme I have, just for fun, hacked the reader to make (#:keyword hash) => (hash-ref hash #:keyword) and a 3 arity to a hash-set!.

      Quite handy, it turned out.

      I thought about extending the reader to allow for clojuresque applicable dicts (racket allows for this quite easily), but I never got to it.

      • ScottBurson 285 days ago

        Have you looked at FSet?

    • classified 286 days ago

      I propose naming it "Clôture", for clarity.

      • Scarbutt 286 days ago

        I can understand escaping from Java enterprise hell to Clojure but devs using a CL runtime will just write CL?

        • simongray 286 days ago

          Clojure is functional to the core, is built on its own seq abstraction rather than cons cells, and uses persistent immutable data structures for everything. It's not at all the same language as Common Lisp.

          • hombre_fatal 285 days ago

            Common Lisp just feels like any other imperative dynamically-typed language to me.

            Clojure on the other hand compelled me enough to learn lisp and emacs.

            • dkersten 285 days ago

              Same. I always liked the idea of lisp, but Scheme seemed like a nicer lisp to me than CL, but never grabbed me enough to really spend time with it. Clojure changed that completely to the point where it’s now my most used (but not only) language.

          • iLemming 286 days ago

            Clojure is a hosted language, today it can host on top of JVM, JS, BEAM or .NET CLR engine. It also can interop with Python code. Getting it to run on CL runtime was just a matter of time.

          • kick 286 days ago

            Why “Cloture”?

            Beside the obvious: cloture is a parliamentary procedure to end debate on a subject, and I would like to end certain debates. Yes, Common Lisp is “modern.” Yes, Clojure is a Lisp.

            While this doesn't actually explain why the person started the project itself, it does shed light on one possible explanation.

            • brlewis 286 days ago

              It does seem like something motivated more by a desire to make it exist than a desire to use it. Also to have a project where both your program and your programming language are lispy.

              • PuercoPop 286 days ago

                The author has stated in Reddit that their goal is able the insta parse library.

                • ruricolist 285 days ago

                  I have actually reached the point of being able to load Instaparse.

            • dimitar 285 days ago

              Clojure is different from CL, it is much more opinionated in favor of a style of functional programming. I can understand wanting to enforce this particular style of programming while using Common Lisp's great native compilers.

              • mikelevins 285 days ago

                It's not just the compilers. Clojure is nice in its way, but it lacks some essentials of Lisp programming. For a while now I've been wishing for a Clojure implementation that is a proper Lisp--one that restores what Clojure has lost. One that has, as I wrote elsewhere, Lisp's flesh and bones.

                I'd also like it to conform well enough to the JVM implementation that I could use my full-blown Lispy Clojure for development, and still deploy with confidence on the JVM Clojure.

                Obviously, someone's going to want to know what I think is missing from Clojure, so following is a partial list. These are things I've been accustomed to relying on for decades now, and that I always miss keenly when working with Clojure.

                Instant startup with my environment already customized to my specifications.

                Comprehensive reflection with full UI support. Support for inspecting absolutely everything in the runtime, with the ability to interactively edit everything you inspect.

                The ability to reproduce my whole development environment and its entire dynamic runtime state on a different account or machine simply by copying over a file.

                A real Lisp repl that can inspect and redefine everything in memory, save and restore the complete dynamic state of the running system, and deliver a finished executable from an expression typed at the repl. In short, anything you can do with the Lisp, you should be able to do from within the Lisp, by evaluating an s-expression at the repl.

                Proper Lisp-style error-handling. The ability to catch any error or exception in an interactive breakloop, walk up and down the call stack, inspect and modify variables, types, and functions, and resume execution with the changed definitions just as if the pending function had been called with them in place originally.

                People rightly complain about Clojure's atrocious error messages. They've gotten better, but they're still very far from what I expect in a Lisp error-handling system. I want the real thing: when something unexpected happens you get a proper Lisp-style interactive breakloop from which you can see and modify everything on the call stack and, indeed, everything in the running system, and you can tell the runtime where to resume execution from, either interactively or under program control.

                A runtime designed with the understanding that you're going to be writing your application by changing it while it runs. Lisp and Smalltalk have had these features forever. Apple's Dylan and SK8 had them. It's disappointing that Clojure doesn't.

                Common Lisp, for example, defines a standard protocol that enables you to redefine classes with the confidence that the Lisp will automatically update any existing instances to conform to the new definition. When Lisp can't figure out how to make things consistent, it doesn't quit or spew a dead stack trace; it starts a breakloop that captures the pending call stack. As I mentioned before, you can use the breakloop to inspect everything on the stack, make whatever changes are needed, and then resume computation with the changes in place.

                I wish it were as easy to subsume foreign type systems into Clojure as it is in Common Lisp. Clojure's Java interop is good, but it's special. You have the nice Java interop, but you don't have a standard API for building your own equally-nice interops. Compare with Clozure Common Lisp's Cocoa interop. It's also good, but more importantly, it's built on common APIs (CLOS and the MOP) that you can use to make your own interop subsystems that are just as good.

                To me, these are table stakes for a Lisp. I've already had them for decades. Working in Clojure means I have to give them up, and the whole time I'm missing them. A Lisp that doesn't know how to do them is only part of a toolbox. Whole drawers of essential tools are missing. I mean, sure, you can build apps with them. And you can build a house with stone knives and bearskins, but if you've used a proper toolbox before, you'll be missing it the whole time.

                If there were a Clojure implementation with these features, then I wouldn't miss Common Lisp so much the whole time I'm working in Clojure. I probably wouldn't spend as much time looking forward to when I can get back to using a real Lisp. I'd get more done faster and with greater joy in my work.

                I'm sort of hoping Cloture will turn out to be a step in the right direction.

              • zelcon 286 days ago

                ABCL exists and is easier to use java libs from than Clojure

                • dkersten 285 days ago

                  But does it have the things that make me use Clojure? (Immutable default data structures, sequence abstraction, general philosophy behind things eg data > functions > macros)

                  • lvh 285 days ago

                    It does not seem easier to me to use Java libs from ABCL than Clojure.

                    Some Java:

                      public class Main {
                        public int addTwoNumbers(int a, int b) {
                          return a + b;
                    Calling it from ABCL:

                      (defun void-function (param)
                        (let* ((class (jclass "Main"))
                               (intclass (jclass "int"))
                               (method (jmethod class "addTwoNumbers" intclass intclass))
                               (result (jcall method param 2 4)))
                          (format "calling addTwoNumbers(2, 4): ~a~%" result)))
                    Calling it from Clojure:

                      (defn f []
                        (prn "calling addTwoNumbers(2, 4): " (.addTwoNumbers (Main.) 2 4)))
                    Note that the Clojure version creates the object, and the ABCL version doesn't do that for you; you still need to instantiate a Main yourself.
                    • fmakunbound 285 days ago

                      Something like:

                          (require :jss)
                          (defun f() 
                            (format t "calling addTwoNumbers(2, 4): ~A" (#"addTwoNumbers" 'Main 2 4))
                      • lvh 283 days ago

                        Thanks! I didn't know about :jss and will happily concede that's equivalent to the Clojure version.

                    • bitwize 286 days ago

                      So does/is Kawa.

                    • globular-toast 285 days ago

                      I would rather use Clojure's fantastic immutable data structures than CL's rather odd ones (like hashmap, in particular).

                      • Jach 285 days ago

                        What's so odd about CL's hash table? Anyway, you can have immutable sets/maps/seqs with one quicklisp call:

                        • pfdietz 285 days ago

                          Hah! I've been using/committing to that lately. I think the GitHub repo is where quicklisp pulls from:


                          • ScottBurson 285 days ago

                            Yes, and let me take the opportunity to thank you! (Have to do it here since you've done such a good job of hiding your email address :-)

                            I wish I had time for FSet work, but I just don't right now.

                          • TurboHaskal 285 days ago

                            There is absolutely nothing wrong with Common Lisp's hash tables. They are fast and rock solid in all implementations I've tried.

                            The author drank too much Kool aid or is probably still in his functional programming honeymoon phase so anything mutable is of course bad and odd.

                            • Jach 285 days ago

                              They did say "odd" not "wrong" so I'd like to hope the feeling is for slightly less dismissable reasons than an FP fetish. What I would have answered if you asked me years ago would be something like how when you come from another dynamic language you immediately notice the lack of data literal syntax without bringing in your own few-lines reader macro, then there's the need to understand the types of equality predicates, and finally the need to get used to the setf conventions. Once you get used to things though, most of which is just getting used to CL more so than its hash table choices, and discover things like alists or plists as a simpler and syntactically convenient out-of-the-box way of representing the handful of hard-coded key-value pairs which tend to make up a tiny portion of a program, the oddness is replaced by appreciation. So I wonder if the GP thinks along these lines, or some other oddity.

                              • patrec 285 days ago

                                There's plenty wrong with hashtables in Common Lisp, they feel like an ugly bolt on. There's no convenient way to construct them or look at them (they're not readably printable, and you can't fix that yourself either). Even checking key presence in a hashtable requires multiple-value contortions.

                                On a more fundamental level, they also suffer more than sequences from the fact that common lisp has no well thought out concept of equality.

                              • pepper_sauce 285 days ago
                          • TurboHaskal 285 days ago

                            I had enough disappointments with Clojure's performance to ever touch it again, and that was with JVM's state of the art garbage collectors. Just the idea of running idiomatic Clojure with SBCL's makes me shudder.

                            • scythe 285 days ago

                              If Mike Pall is to be believed, dynamic languages do better with specialized environments, which is why he thought LuaJIT 2 was a better strategy than Lua on the JVM or LLVM. SBCL may or may not incorporate similar optimizations, but it's definitely built to handle Lispy weirdness from the ground up.

                              There was briefly an attempt by Raphael Amiard to compile Clojure to Lua, which could also test this theory, but it was never completed and I doubt anyone else feels like doing that much work for free:


                              • galfarragem 285 days ago

                                > compile clojure to lua

                                That's fennel:

                                • scythe 285 days ago

                                  Writing your own language is more characteristic of the kind of work people do for free -- unfortunately, it creates a high barrier to entry. You can't tap in to Clojure's existing library ecosystem, for example. Does Fennel have instaparse? Leiningen? (And sure, you could use Lua libraries, but they're built to work like Lua libraries, so at that point you might as well be writing Lua.)


                                  Of course this same critique could have been leveled at Clojure from its outset, but Hickey has really stuck with it for the long haul and made Clojure a very good language in itself as well as just being a Lisp on the JVM (and it even has sort of an ecosystem, a rarity among Lisps and what made me interested). I thought a few times about writing my own language when I was younger, but I realized it's a life-consuming task, and not something you can just do. But I do wish the author the best.

                          • lbj 285 days ago

                            > "Beside the obvious: cloture is a parliamentary procedure to end debate on a subject, and I would like to end certain debates. Yes, Common Lisp is “modern.” Yes, Clojure is a Lisp."

                            Im interested to see how well this will fare. Personally, Im a little depressed that Clojure-in-Clojure has been postponed for nearly a decade. It would have made changing compiler targets much easier.

                            • e12e 285 days ago

                              > Clojure namespaces are just packages. But of course Clojure is case sensitive, so you will need to use pipe characters to call, for example, |clojure.core|:|cons|.

                              Is this an implementation detail or a common lisp idiom to work with case (in)sensitivity?

                              • globular-toast 285 days ago

                                The statement you quoted is a bit misleading. Common Lisp is case sensitive just like Clojure, but unlike Clojure all of the standard library is spelt with upper case and the reader automatically concerts symbol names to upper case, unless you quote them, like in the given example.

                                • mikelevins 285 days ago

                                  You can also use the standard accessor readtable-case to tell your Lisp whether you want it to convert case or not.

                              • globular-toast 285 days ago

                                Can't wait to have a go with this. Common Lisp is a joy to work with but I would actually prefer to use Clojure because I like functional programming. But while I tried to get into Clojure I just hate having to run the JVM and having Java interop is worthless to me. So being able to use the Clojure functionality in a more familiar environment might be perfect.

                              • cellularmitosis 286 days ago

                                Don't some Common Lisp implementations support heap snapshots? Might improve Clojure startup time.

                                • lvh 285 days ago

                                  They do, and, thanks to GraalVM’s Substrate compiler, the same technique is also available for Clojure directly on the JVM. I imagine you’re going to pay some cost in both cases, perhaps in different places. (I imagine with Cloture it might be at development time, but once your program works the compiler won’t break it—-with Graal you don’t notice a thing during development but the compiler breaks plenty of programs.)

                                  • lispm 285 days ago

                                    These are different things.


                                    Where SBCL just adds some compiled code, writes a memory dump out in a second. Then this can be run as a new SBCL with added code - it still includes the compiler, and all other stuff. One can also then add more code and write another image... The new image is just like the other one, just with more code and otherwise the usual feature set.

                                    This includes the whole SBCL and my code, not just a static application with limited dynamic features.

                                    Many Common Lisp systems have a way to write a snapshot of the developed environment, which then starts in subsecond.

                                    Additionally some Common Lisps have ways to do application or shared library delivery, which can remove dynamic features, remove parts of the development tools, remove unused code/data, etc. This may be similar like doing heap dumps, but with added optimization phases.

                                  • yvdriess 286 days ago

                                    Yes, most have a variant where you can dump the image, maybe with a tree-shake even:

                                  • voldacar 286 days ago

                                    Cloture and Clojure, not to be confused with Clozure

                                    • hellofunk 286 days ago

                                      Or Google Closure compiler.

                                      • vvern 286 days ago

                                        Which is a key enabling technology of Clojurescript (cljs).

                                      • ruricolist 285 days ago

                                        I did actually do most of the initial development on Clozure.

                                      • ScottBurson 285 days ago

                                        Glad you've found FSet useful! I'm curious what the difference is in map equality semantics.