[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)
 
web link (joker-lang.org)
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)