This time we have the Clojure enthusiast and Co-author of Clojure Programming, Christophe Grand in our Corner. He is a core contributor and right now working on ClojureDart.
Ela: Hi Cristophe, thank you again for accepting our invitation to our Clojure Corner
Let me introduce our team, Enyert, and Alex our developers who are going to lead this corner Hope you all have a great time here.
Enyert: Hello Ela, Christophe, and Alex, thank you for letting me participate in this space!
Christophe: all
Alex: Hello Christophe,
this is meant to be in async format… so take your time with answering the questions, thanks
Can we maybe start with some info about you and your background?
How did you start with programming and what was that like?
What was your path to Clojure and what drew you to it?
Thanks
Christophe: Hi!
I was an 8-bit kid. I got fascinated by programming in elementary school where we were introduced to turtle graphics in Logo. A couple of years later I got my own computer where I learned Basic and Z80 assembly, then Pascal (Think, Turbo, and Delphi) and C once in high school.
Right after high school, I had my first CS courses which were taught using Caml (the ancestor of OCaml) and the functional programming immediately clicked for me.
Fast forward almost 10 years, I was working for a consultancy mostly using Java when I read Douglas Crockford’s articles on JS (the little javascripter amongst theù) and understood that it had some functional programming in it. Next project I was working on we used Rhino to run JS on the JVM instead of Java!
This experience taught me lessons (JS semantics and shared memory concurrency don’t go well together) and left me looking for another language this is how I stumbled on Clojure when it was 3 months old. It had all I was looking for (dynamic, JVM, metaprogramming, concurrency story) and even persistent data structure, a feature I had daydreamed about without ever managing to find the relevant papers. The nascent community was very welcoming, I learned a lot.
Enyert: Hey Christophe, From my side, as I did some experiments with ClojureDart and even created a post related to our blog a few months ago, I wanted to ask you about the state of the project. I see the project is active in GitHub and I saw an amazing presentation at Clojure/Conj 2023, so are there some incoming features that you want to share with us? We can peek the git commits but it is always better to read/hear this info from you.
Christophe: The big features are: self-hosting and the repl.
Implementing multimethods is also a nagging thought: not that many people complain and workarounds are found but they block some porting projects.
There are many smaller things we’d like to improve like the support for Dart extension methods or places where type-hinting shouldn’t be required etc. And better tooling support (LSP, debugger, etc.).
Recently with the growing interest in ClojureDart, we’ve spent more time supporting users, at the same time we are lucky to have a few sponsors and Clojurists Together funding, which allow us to be able to both support users and keep investing our time on ClojureDart.
It’s still a side project even if we would love to be half or full time on it!
Enyert: I think one of the most popular things about ClojureDart is the integration with Flutter, it brings to Clojure another option to develop multi-platform applications. I already tested a few basic sample applications and it was great, my question is: What are the main limitations(if any exist) of using ClojureDart + Flutter in the production environment? I have seen this integration in action with the Roam Research mobile app and it was a good user experience in my opinion, but I want to know if there is any particular mobile/web feature to be careful of at the moment of the implementation.
Christophe: Our goal with ClojureDart is that you should never have to drop to Dart. If you encounter a limitation we will consider it a bug.
This doesn’t mean we replicate every feature of Dart, only those necessary for interop. A good example is Dart 3: Dart 3 introduced records (immutable tuples/product types) and pattern matching.
Records we had to support them because APIs may exoecg or return records. So we added a literal reader and type inference for them.
Pattern matching is an implementation detail: when you invoke a function you can’t tell if it was written with ifs or pattern matching.
Another example: in Dart, a method may have both positional and named parameters. Up to a minor release (2.17) you have to pass positionals first and then nameds; since this release nameds and positions can be mixed. This is not a change we mimicked because it has no impact on the ability of ClojureDart to reach Dart and Flutter APIs.
Another example of our design philosophy towards interop is deftype/reify: they are both more potent than in Clojure. In addition to implementing protocols and interfaces, they can extend classes even abstract ones, apply mixins, etc. Again this was done so that one doesn’t have to drop to Dart. And we dogfood: all of the ClojureDart runtime (all fns, all collections) is written in ClojureDart.
Enyert does it reply you your question? I didn’t talk about the Roam Mobile app because the current version relies heavily on a webview – there are native screens but the core of the app lives in the webview. A new one is in the works but can’t publicly talk about it yet.
Enyert: Yes! We really appreciate your response, thank you again. It is really important to know a little bit more about the design of ClojureDart. You provided good examples to understand your vision, so this helps us to make better decisions at coding time.
- I have been looking at your project xforms because it contains new useful types of transducers to be used in many different situations. I know one of the big advantages of using transducers is the decoupling of the transformation and the input/output. I think for this reason, channels have even deprecated their own core functions(map, filter, etc.) implementations. But what about the usage of thread-last macro vs. transducers? I am asking because I have been using thread-last(->>) macro in a personal project that involves many steps of data transformations, so maybe I can refactor some of the transformation logic using transducers.
- Another project that I encountered on the way is megaref which is being used by one of our potential customers. Please, can you give me more information about this one? I think a good use case for this one could be to store some type of projection from a main database to a megaref instance. Am I correct? Can you mention what was the main factor of inspiration for this project? Please, Can you describe another use case for megaref?
Christophe: On transducers. You are right about channels’ own functions depreciation: once you start implementing map, filter, etc. on many concrete types (seqs, chans, java streams, spark rdds…), it feels redundant and even besides the mere effort of code duplication there’s the more profound problem of reuse.
Each API (in the broad sense of the set of functions) is its own small language and code that you write on top of it won’t work with an almost similar set of functions (code which transformed seqs didn’t work on chans etc.). Transducers solve this by allowing, like you said, to express transformation irrespective of the context.
This allowed us in ClojureDart for example to have a stream function to apply transducers to Dart’s streams. So that we don’t have to depend on Streams versions of `map, filter, etc.
That was from a design point of view, now from an efficiency point of view, transducers are generally more efficient because they allocate less short-lived objects and thus put less pressure on the GC and memory caches. That’s why rewriting a seq pipeline (using ->> or regularly nested) to transducers might be a good idea. As usual, it depends on various factors: is it on a critical path? Is the seq overhead significant compared to the actual computation? Does the underlying context optimized for reduce? etc.
As for megaref, I’m delighted to know some people have put it to work. To me, it was mostly an experiment in composition and design to see how to achieve higher throughput through the STM or atoms.
Which one does your potential customer use: megaref or megaatom?
The main factor of inspiration was lock striping and I wanted to adapt the idea to STMs and atoms.
Anyway: having a single atom holding the whole state of an application is really powerful since you can snapshot the whole state, so I wanted to see if we could have an API that would allow you to still pretend to have a single atom but be finer-grained on updates so as to not tretry that much often.
The experiment was successful.
Nowadays, I believe a weakness of the single-atom approach is that data inside it is generally hierarchical (maps of maps of maps) while it should be a relational db.
I have many ideas about a client-oriented db that I’d like to explore (I only need rich patrons to fund this work ).
Enyert: Christophe, Thank you again for your collaboration in this Clojure Corner. From my part, I have only one more question left, and it is more related to your amazing eye to collaborate with the Clojure community. Do you have some advice for people who want to contribute to the community? I am asking this because I see your contributions have been huge, and accurate, and have added a ton of innovation to the Clojure ecosystem, so you are a wonderful reference.
Christophe: Thanks. my advice would be:
- either contribute to projects you are a user of and on issues you feel connected to
- or start a new project with your own wild ideas.
While you are waiting for the next Clojure Corner you can read our past Corner with Michiel Borkent.