[HN Gopher] Joker: A small interpreted dialect of Clojure writte...
       ___________________________________________________________________
        
       Joker: A small interpreted dialect of Clojure written in Go
        
       Author : rcarmo
       Score  : 155 points
       Date   : 2021-09-02 21:57 UTC (1 days ago)
        
 (HTM) web link (joker-lang.org)
 (TXT) w3m dump (joker-lang.org)
        
       | Joker_vD wrote:
       | Hey! That's _my_ name! :) Incidentally, I also have a small
       | language also implemented in Go although it 's actually called
       | "".
       | 
       | So it's a Clojure REPL _and_ linter /formatter? Why have REPL at
       | all then?
        
         | stcredzero wrote:
         | _Why have REPL at all then?_
         | 
         | To many, interactive development and debugging is the main
         | point of the Clojure REPL. (Also true for many dynamic
         | languages.) I take it you don't code and debug using such
         | techniques. Some feel they are a _revelation_.
        
       | Zababa wrote:
       | This would go really well with https://cyclone.thelanguage.org/.
        
         | eggy wrote:
         | 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.
        
           | pjmlp wrote:
           | 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.
        
           | bruce343434 wrote:
           | What made you give up on rust?
        
             | truth_ wrote:
             | For me, it was "no need". I liked everything I saw. I just
             | had no need to write Rust.
        
       | emacsen wrote:
       | The Eclipse Public License is a showstopper for me. :(
        
         | vnorilo wrote:
         | It's quite common in Clojure circles. Care to elaborate? Not
         | copyleft?
        
       | didibus wrote:
       | Some clarification for those interested:
       | 
       | Joker is not a dialect of Clojure hosted on the Go runtime. Joker
       | is a dialect of Clojure using its own interpreted runtime, that
       | happens to be implemented in Go.
       | 
       | That means you cannot use Joker to build statically linked Go
       | binaries and you cannot interop with Go from your own code.
       | 
       | And if you're interested in the idea of having an interpreted
       | implementation of Clojure, for scripting use cases and fast
       | startup, I would recommend you look into Babashka instead of
       | Joker: https://babashka.org/
       | 
       | Joker was the best interpreted Clojure prior to Babashka, and
       | nowadays, I would recommend Babashka over Joker all the time.
       | 
       | The reason Babashka is better than Joker for this is that it is
       | implemented in Java, so it can directly reuse the existing
       | Clojure implementation and libraries as-is as part of its
       | interpreter. That allowed it to quickly surpass Joker in the
       | amount of Clojure features it support and libraries it exposes,
       | and continue to quickly keep up with Clojure. I'd also say the
       | main developer behind Babashka is more active.
       | 
       | Even though Babashka is implemented in Java, it is still a single
       | statically linked binary runtime that starts extremely fast and
       | has a small memory footprint.
       | 
       | Kudos to Joker though for pioneering the idea and leading to
       | Babashka.
       | 
       | Edit: And if you were excited because you thought this would let
       | you write compiled statically linked binaries in Clojure, well
       | for that you want to look at GraalVM native compilation which
       | lets you compile real Clojure programs as low memory, fast
       | startup, statically compiled single binaries:
       | https://github.com/lread/clj-graal-docs
        
         | didibus wrote:
         | A small correction, Babashka is implemented in Clojure, not
         | Java, but, as such, it can also make use of Java libraries as
         | part of its implementation. So Babashka can bundle both Clojure
         | or Java libraries inside itself.
        
       | cutler wrote:
       | For me the most interesting compilation target for Clojure is
       | Dart. Writing Flutter apps in Clojure would be the dog's
       | spherical objects. David Nolen thinks likewise and has commented
       | on it in a recent defn podcast. Christophe Grand and Baptiste
       | Dupuch are the brains behind it:
       | https://twitter.com/cgrand/status/1350063059864346624?lang=e...
        
         | didibus wrote:
         | Has Flutter really become that popular and awesome? I feel I'm
         | still seeing a lot more of React Native or Electron and other
         | JS based front-ends in the wild, for which ClojureScript is
         | best suited. I'm not sure I'm convinced Flutter will be able to
         | break through those even with Google behind it.
        
           | nkozyra wrote:
           | I feel like it's picking up steam.
           | 
           | I've written a few things in it and found fewer footguns than
           | React Native.
           | 
           | Granted, it comes with its own set of idiosyncracies,
           | breaking version changes, half baked community libraries and
           | developer ecosystem, but that's par for the course.
        
       | masijo wrote:
       | 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.
        
         | skybrian wrote:
         | 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?)
        
           | AtlasBarfed wrote:
           | What C tricks are used? I'd be fascinated to know some
           | details.
        
             | spijdar wrote:
             | 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
        
             | skybrian wrote:
             | 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
        
               | geeio wrote:
               | I think you could do the pointerless version (the one
               | using an int32 index) of this in go via unsafe casting.
               | 
               | I'll try it out later
        
         | bpicolo wrote:
         | Can do that with Graal these days:
         | https://github.com/BrunoBonacci/graalvm-clojure/blob/master/...
        
           | uDontKnowMe wrote:
           | I explored this briefly but it ended up taking about 25
           | seconds to compile hello-world in Clojure. That struck me as
           | causing a lot of pain to develop for.
           | 
           | That, combined with how you'd lose access to a lot of the
           | java library ecosystem, makes it less appealing to me to
           | develop on.
        
             | dig1 wrote:
             | 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 graal.
        
               | uDontKnowMe wrote:
               | 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?
        
               | didibus wrote:
               | 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.
        
               | [deleted]
        
               | jgrodziski wrote:
               | 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.
        
         | zerr wrote:
         | What would be the advantage over other Lisps if you won't be
         | able to leverage JVM ecosystem?
        
           | didibus wrote:
           | Depending who you ask, Clojure provides some benefits over
           | other Lisps unrelated to its ability to leverage the JVM.
           | 
           | Some of those are:                   - Default efficient
           | immutable collections         - Good support for safe
           | concurrency and multi-core programming         - Extended
           | homoiconicity to include maps, vectors and sets as well as
           | lists         - Convenient short lambda syntax         -
           | Extensible data representation         - Distinguishes
           | between nil and empty list         - Distinguishes between
           | keywords and symbols         - Real boolean types         -
           | Function arity overloading         - Well thought out
           | abstractions and set of standard functions: collections are
           | abstractions, lazy sequences, transducers, relational set
           | manipulation, etc.         - Only two kinds of equality: by
           | value or by reference, with the former being the default
           | - Is a lisp-1 like Scheme, but also with more batteries
           | included like Common Lisp         - Slightly different visual
           | appearance in its syntax, due to support for literal maps and
           | vectors as well as lists, which can make things easier to
           | visually parse.
        
           | leephillips wrote:
           | Some people like Clojure's data structures, which add a
           | couple of things to lists. Also, there's ClojureScript, which
           | is huge. And, ideas such as software transactional memory.
        
         | chitowneats wrote:
         | Have you tried Babashka? https://github.com/babashka/babashka
        
           | dimitar wrote:
           | babashka also has a performance non-goal
        
             | chitowneats wrote:
             | Ah. You are correct.
        
             | Borkdude wrote:
             | The performance non-goal is there to lower expectations
             | compared to compiled Clojure. Babashka's author (me) does
             | try to make it as fast as possible within the constraints
             | it's implemented in.
        
             | didibus wrote:
             | That's true, but in my poor man tests, Babashka was in the
             | ballpark of Python for me.
        
               | chitowneats wrote:
               | I was surprised when I realized I had overlooked the
               | performance non-goal considering how fast it has been in
               | my limited experience.
        
               | didibus wrote:
               | Ya, it even supports multi-core programming, so I bet for
               | some use case it can outperform Python by parallelizing.
        
         | didibus wrote:
         | 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
        
         | vnorilo wrote:
         | 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? :)
        
           | geokon wrote:
           | 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 syntactic sugar to
           | make it more light weight like Clojure?
           | 
           | With hooking into GDB you could probably make a REPL as
           | well.. GDB has live code reload, introspection - you even get
           | state of crash and sane stack traces :)
        
             | dig1 wrote:
             | > 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
        
               | geokon wrote:
               | I mean, I didn't say it's undocumented behavior :) but
               | when the default performance of a basic function like
               | `first` is not O(1) then that strongly suggests that
               | performance is secondary to composability/flexibility
               | 
               | (I'm actually not clear why it doesn't alias to `(nth ..
               | 0)` .. I'm sure there is a good reason)
               | 
               | In Clojure you don't really reason about vector's run
               | time behavior at all like in C++. You can't even ensure
               | cache locality b/c different items can have any size. You
               | can try to be clever and fall back to Java arrays, but
               | the documentation says "It is recommended that you limit
               | use of arrays to interop with Java libraries that require
               | them as arguments or use them as return values." and the
               | language doesn't provide the syntactic sugar for their
               | easy use with the other data structures. So it kinda
               | looks like the language is actively trying to discourage
               | you from using an efficient data structure.
               | 
               | My previous post was trying to say that maybe you could
               | elevate arrays to be more first class, make `first` and
               | company O(1), maybe lose some flexibility/composability
               | in the process but find some performant middle ground
               | between Clojure and C++. I have no idea if this is
               | actually achievable.
               | 
               | btw, STL vector resizing is a total non issue ... Most of
               | the time you'd just allocate sufficient space beforehand
               | (then it's truly zero cost). If you need to do something
               | dynamic and care about when it resizes then you can just
               | resize manually as you go (that's also zero cost - b/c
               | this resizing is unavoidable and part of your algorithm).
               | The default auto-resizing behavior just handles the
               | resizing for you in a sane O(1) way
        
               | vnorilo wrote:
               | I think zero cost in this context means compile-time
               | polymorphism (templates) instead of runtime polymorphism
               | (virtual functions).
        
             | vnorilo wrote:
             | 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.
        
               | didibus wrote:
               | Interop with C is pretty decent, but if you want to make
               | a C++ hosted Clojure dialect I'm all for it! The more the
               | merrier!
        
               | geokon wrote:
               | It does already sort of exist: https://ferret-lang.org/
        
               | geokon wrote:
               | 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.
        
               | yawaramin wrote:
               | Check out https://github.com/carp-lang/Carp
               | 
               | From the readme:
               | 
               | > The key features of Carp are the following:
               | 
               | > - Automatic and deterministic memory management (no
               | garbage collector or VM)
               | 
               | > - Inferred static types for great speed and reliability
               | 
               | > - Ownership tracking enables a functional programming
               | style while still using mutation of cache-friendly data
               | structures under the hood
               | 
               | > - No hidden performance penalties - allocation and
               | copying are explicit
               | 
               | > - Straightforward integration with existing C code
               | 
               | > - Lisp macros, compile time scripting and a helpful
               | REPL
        
               | aidenn0 wrote:
               | Consider perhaps https://github.com/clasp-
               | developers/clasp which is a common-lisp specifically
               | designed to interop with C++.
        
           | jb1991 wrote:
           | It would seem to me the most significant challenge would be
           | having functional data structures in C++, that would be a
           | non-trivial effort I would think. Even if you were to use
           | other libraries that experiment with this. Without those, the
           | language just doesn't make any sense.
        
             | toymachine1974 wrote:
             | My Park language does this
             | https://github.com/toymachine/park-lang Currently the
             | syntax is mostly JS but the semantics is all clojure. I
             | recreated the map and vector datatypes in c++ by adapting
             | clojures Java code. it needs a GC though which is another
             | big part of the implemention of Park
        
             | vnorilo wrote:
             | Yeah, that was the problem with most attempts I saw,
             | unordered_map won't work. I did write a persistent hash map
             | and vector [1], but ended up mostly using them in C++
             | projects. I wouldn't advise anyone else to use them at this
             | level of testing/maturity though. Writing them was super
             | interesting.
             | 
             | 1: https://hg.sr.ht/~vnorilo/pcoll/browse (not production
             | quality)
        
             | bertmuthalaly wrote:
             | Immer [0][1] seems like a really serious attempt at
             | bringing high-quality persistent data structures to C++.
             | 
             | [0]: https://www.youtube.com/watch?v=sPhpelUfu8Q
             | 
             | [1]: https://sinusoid.es/immer/
        
       ___________________________________________________________________
       (page generated 2021-09-03 23:02 UTC)