Live data from Hacker News

Joker: A small interpreted dialect of Clojure written in Go

joker-lang.org

31–40 of 73 posts

Re: Joker: A small interpreted dialect of Clojure written in Go

#31
post #3

This project looks awesome, though I'm a bit sad that "performance" is a non-goal as stated in the README. Clojure being able to compile to a single shippable binary is just what I need in my life. Kudos to the author.

You can just use GraalVM for that. Also, Joker does not let you do what you want, it is not Clojure hosted on the Go runtime, it is a Clojure interpreter runtime implemented in Go, so with Joker you always need the joker binary.

If you want to produce native shippable statically linked binaries with Clojure look at GraalVM native compilation:

https://github.com/lread/clj-graal-docs

Re: Joker: A small interpreted dialect of Clojure written in Go

#32
post #30
post #6

Earlier quoted context omitted.

babashka also has a performance non-goal

That's true, but in my poor man tests, Babashka was in the ballpark of Python for me.

I was surprised when I realized I had overlooked the performance non-goal considering how fast it has been in my limited experience.

Re: Joker: A small interpreted dialect of Clojure written in Go

#33

Earlier quoted context omitted.

It’s difficult to write a fast interpreter in Go. It doesn’t optimize interpreter inner loops particularly well, you can’t do the data structure tricks you can do in C, and there is no JIT compiler available like in Java. The best way forward might be an option to compile some Joker code by generating Go code for it. (This is based on writing an interpreter a few years ago. Perhaps Go’s compiler has improved?)

What C tricks are used? I'd be fascinated to know some details.

Caveat: I've barely used Go personally.

Just speculating on what GP specifically meant, but Go lacks the ability to pack structs, and probably lacks the equivalent of tricks like computed gotos [0] to increase bytecode interpreter speeds. In general, Go seems to (intentionally) lack a lot of low-level control of code generation, preferring a "there's one way to do things, and it either works, or it's a bug we'll fix" approach.

Which is probably for the best in "most" software, but interpreters typically use weird hacks to squeeze more performance out of the rock. Wren is a good example [1] of some of these optimizations, and has splendid comments/documentation.

[0] https://eli.thegreenplace.net/2012/07/12/computed-goto-for-e...

[1] https://github.com/wren-lang/wren/blob/main/src/vm/wren_vm.c

Re: Joker: A small interpreted dialect of Clojure written in Go

#34
post #20
post #11

Earlier quoted context omitted.

I have long had the ambition to be the N:th guy to try Clojure on C++. For similar reasons. I have a pretty good idea how to do it, already got HAMT and some macroexpanded code to work. But then you remember how even ClojureCLR struggled at times, and that you'd be lightyears behind that, not to speak of CLJ/CLJS, and alone. Maybe one day? :)

I think recreating Clojure point by point is not optimal. There is some middle ground between full on Clojure and the straightjacket of the STL datastructures. - Clojure is kinda difficult to reason about performance wise. The lazyness.. some weird corner cases (like how `(first ..)` is slower than `(nth .. 0)` - The STL is very strict on the "zero cost abstraction" front. Maybe something like Immer but with some syn…

> some weird corner cases (like how `(first ..)` is slower than `(nth .. 0)`

I would not consider this a corner case; it is stated in (first) docstring. (first) will create seq [1] object, then it will fetch the first element. (nth), on other hand, will not create seq object.

> The STL is very strict on the "zero cost abstraction" front.

Many edge cases which are lost in myriad of classes. For example, vector will allocate more memory than there are actually elements in it, and you never know when it will start resizing it (this is implementation detail). I would not call this as "zero cost abstraction".

[1] https://clojuredocs.org/clojure.core/seq

Re: Joker: A small interpreted dialect of Clojure written in Go

#35
post #23
post #20

Earlier quoted context omitted.

I think recreating Clojure point by point is not optimal. There is some middle ground between full on Clojure and the straightjacket of the STL datastructures. - Clojure is kinda difficult to reason about performance wise. The lazyness.. some weird corner cases (like how `(first ..)` is slower than `(nth .. 0)` - The STL is very strict on the "zero cost abstraction" front. Maybe something like Immer but with some syn…

You see, I'd like to write more of my software in Clojure (or any functionally inclined Lisp). If there was a really good interop story with a non-gc:d native lang I could do more of it. Clojure is a hosted language, so hosting it on C++ (or Rust etc.) would be sweet for me personally. Also, I like compiler projects.

I've never had to use it myself, but what part of the JNI/JNA is problematic?

One thing I'd sorta fantasized about is (in a very hand wavey way) using Clojure + ClojureCL to run OpenCL kernels. In theory if you were to bundled your JAR with POCL.. you could selectively run your tight-loops/kernels on the CPU or GPU. So it'd be effectively like launching little C snippets. Furthermore, from what I remember, kernels are compiled and executed at run time - so it'd really fit with the whole REPL workflow and they would effectively simply be inline strings of C code that get executed

I think the main catch for the moment is that ClojureCL uses a MKL matrices to talk to the OpenCL driver (driver? dispatcher? I might be mixing up my terms) - but that could be modified to a JVM container

Probably my own bias - but the only time I've really been let down by Clojure and missed native was when I need to optimize a tight loop. Once you optimized Clojure past a certain point (to eliminate reflection, boxing issues, destructuring etc.) it starts to get ugly.

Re: Joker: A small interpreted dialect of Clojure written in Go

#36
post #25

Earlier quoted context omitted.

True, but you don't develop that way using graal. You develop java apps the usual way (eclipse, emacs/vim/maven, whatever) and create a fat jar (or uberjar). When you make sure the application is working, compile it with graal, creating a static binary. Clojure cycle is even faster because you keep REPL open and evaluate code directly while application is running. After that, you build uberjar, then compile it with g…

Would there not be a risk that you may get something working nicely in openjdk and then you go to do a native compilation, and it turns out that it doesn't work due to some new method call which uses reflection or something?

Reflection is supported by GraalVM native compilation, but you need to declare what classes you need reflection support for at compilation.

What you must understand though, is that you're asking for something contradictory. You cannot get runtime eval and code linking and also deliver a statically linked compiled machine code binary.

That's why statically linked to machine code binary languages don't offer dynamic code generation, linking and reflection that can't be declared or performed at compile time.

This is true for GraalVM. It means when you develop Clojure for GraalVM native compilation you can't leverage such dynamic behavior as well.

Though you can embed the SCI Clojure interpreter inside your application and do dynamic code evaluation with it at runtime.

Re: Joker: A small interpreted dialect of Clojure written in Go

#37

Earlier quoted context omitted.

It’s difficult to write a fast interpreter in Go. It doesn’t optimize interpreter inner loops particularly well, you can’t do the data structure tricks you can do in C, and there is no JIT compiler available like in Java. The best way forward might be an option to compile some Joker code by generating Go code for it. (This is based on writing an interpreter a few years ago. Perhaps Go’s compiler has improved?)

What C tricks are used? I'd be fascinated to know some details.

For one thing you have C unions. You can store different types in the same place and use information stored elsewhere to know what the bits really mean. It’s horribly unsafe compared to a Go interface or the tagged unions in other languages, but uses less space.

Also, there is the NaN boxing trick [1], which allows you to store a value that is essentially a tagged union that is either a double, a pointer, or an integer, in the same space as a double. To get these in a safe language, they essentially have to be built in already.

Go’s interface type is very high overhead compared to the union types used in other languages because it uses an entire pointer as a tag. Though, it only matters in special cases like interpreters. Most performance-intensive code isn’t nearly so dynamic.

[1] https://sean.cm/a/nan-boxing

Re: Joker: A small interpreted dialect of Clojure written in Go

#38
post #15
post #2

This would go really well with https://cyclone.thelanguage.org/ .

I realize the project is no longer being developed by the original group, but is there a recent fork by others or version of cyclone for 64-bit computers? I've given up on Rust for me personally, but I think it is a great PL and effort. I am still playing with Zig, but I would love to just leverage my C background, and see if Cyclone eases me away from always coding in C.

If you had issues with Rust, with Cyclone it would be even more complex, given the way its type system works, in fact Rust is must more ergonomic from what I can gather from the surviving Cyclone papers.

Re: Joker: A small interpreted dialect of Clojure written in Go

#39
post #25

Earlier quoted context omitted.

True, but you don't develop that way using graal. You develop java apps the usual way (eclipse, emacs/vim/maven, whatever) and create a fat jar (or uberjar). When you make sure the application is working, compile it with graal, creating a static binary. Clojure cycle is even faster because you keep REPL open and evaluate code directly while application is running. After that, you build uberjar, then compile it with g…

Would there not be a risk that you may get something working nicely in openjdk and then you go to do a native compilation, and it turns out that it doesn't work due to some new method call which uses reflection or something?

[deleted]

Re: Joker: A small interpreted dialect of Clojure written in Go

#40
post #25

Earlier quoted context omitted.

True, but you don't develop that way using graal. You develop java apps the usual way (eclipse, emacs/vim/maven, whatever) and create a fat jar (or uberjar). When you make sure the application is working, compile it with graal, creating a static binary. Clojure cycle is even faster because you keep REPL open and evaluate code directly while application is running. After that, you build uberjar, then compile it with g…

Would there not be a risk that you may get something working nicely in openjdk and then you go to do a native compilation, and it turns out that it doesn't work due to some new method call which uses reflection or something?

GraalVM issue an error if it can't resolve the proper method call or if another Thread is involved in the computation so I find it pretty safe to use. Of course, GraalVM doesn't exempt you from doing some tests.
Post reply on HN