malli 0.20.1 out! highlights:
β’ new experimental :validate schema for outputting custom validation errors
β’ fix for clojurescript 1.12
https://github.com/metosin/malli/releases/tag/0.20.1
https://github.com/babashka/instaparse-bb: Use https://github.com/Engelberg/instaparse from babashka!
v0.0.7
β’ Bump pod to 0.0.7
β’ Add add-line-and-column-info-to-metadata
β’ Add get-failure
β’ Fix opts passing in parser (e.g. :output-format :enlive)
β’ Support java.net.URL for grammars
And what's cool about this release, you can now run https://clojurians.slack.com/archives/C06MAR553/p1772758259332449 from bb too :-D
bb -Sdeps '{:deps {io.github.babashka/instaparse-bb {:git/tag "v0.0.7" :git/sha "97ed438"} io.github.replikativ/superficie {:git/tag "0.1.11" :git/sha "41433c3"}}}' -e '(require (quote [superficie.parse :as p])) (println (p/parse-string "def x := 1 + 2"))'
Nice work! Parse everywhere...
https://github.com/replikativ/superficie β surface syntax for Clojure During my PhD (in ML) I worked in Clojure while everyone around me used Python. Whenever I tried to show my code in a presentation, a paper, or a whiteboard session I'd lose people at the parentheses immediately. Not because S-exps are bad, but because you can't expect someone to read them on the fly when they've never seen them before. That initial unfamiliarity takes a few days to overcome, but a few days is infinity when you're in a meeting. Superficie is a bidirectional renderer that translates Clojure into syntax that Python/Julia/TypeScript developers can already read:
;; Clojure ;; Superficie
(defn process-users [users] defn process-users(users):
(->> users users
(filter :active) |> filter(:active)
(map :name) |> map(:name)
(sort) |> sort()
(take 10))) |> take(10)
end
It's not a new language - there's no separate runtime or ecosystem. You write Clojure as normal. When you need to show it to someone outside the Clojure world, you render it. The translation is automatic, bidirectional, and roundtrips cleanly (`.clj` β .sup β .clj).
It can also serve as a gentle on-ramp: shown side-by-side with Clojure, superficie helps newcomers build fluency naturally instead of being blocked by unfamiliar syntax.
Tested across 17 real-world projects (core.async, Datahike, Onyx, Malli, Clara Rules, ...) with 98% of files roundtripping cleanly. Includes a highlight.js plugin and an interactive side-by-side demo.
https://github.com/replikativ/superficie https://replikativ.github.io/superficie/examples/side-by-side.htmlCool idea! Have you tested it on someone with paren-positional aversion syndrome?
Not really yet. I don't think people necessarily have paren-positional aversion, it is just hard to do it on the spot and I came across as weird when I expected people to do it. I will test in the next days though.
@pez I fixed reader conditional handling so it does not break and have an example at the end https://replikativ.github.io/superficie/examples/side-by-side.html, but for now functions containing it fall through to the sexp copy case. I can try to improve this if needed, maybe this is good enough though. Getting all the grammar perfect will be considerably harder (maybe doable, but this is why we don't like such syntax, right π ?)
Right, of course. I personally think it reads well, but Iβm also not in the target group.
Hehe, yes. It is always very easy to forget how long it took to learn something and how difficult it felt in the beginning. Reading Lisp is not super hard, but when you look at it the first time it looks intimidating, I think. I remember when I first saw Elisp as a not yet real programmer a long time ago, and it just seemed very alien and difficult to even categorize.
@whilo I meant that I want to consumer Superficie as a library from ClojureScript. π
can i see s-expr style UI code like electric, these two functions: β’ https://electric.hyperfiddle.net/fiddle/dustingetz.talks.two-clocks$TwoClocks β’ https://electric.hyperfiddle.net/fiddle/dustingetz.talks.dir-tree$DirTree
if you can crack UI code it might be a really big deal, note you can also use JSX-style inline xml literals if it helps
the reason UI matters is due to how compactly composed and nested it is, UI code is really a lot more dense and intricate than backend code
Awesome! I remember a re:clojure conference where James Gosling gave a talk and basically the only complaint he got about Clojure were parentheses. And I thought to myself: well, what if you could remove them just for reading (think of vim visual mode)?
@dustingetz added a special case for e/defn for now (not ideal, can be generalized later):
e/defn TwoClocks():
e/client(let s := e/server(e/System-time-ms()), c := e/client(e/System-time-ms()):
dom/div(dom/text("server time: ", s))
dom/div(dom/text("client time: ", c))
dom/div(dom/text("skew: ", s - c))
end)
end(special case to make it render like defn that is)
that's not bad
let can be flattened into imperative statements like JS also, the let scope extends the rest of the block
(let [a 1] (let [b 2] (+ a b)) becomes let a=1, b=2; a + b and this covers the happy path, if the let is bounded you need a block scope operator
i.e., this let has spurious indentation that can be eliminated to feel like JS
defn safe-divide(a, b):
if zero?(b):
{:error "Division by zero"}
else:
let result := a / b, rounded := Math/round(result):
{:value result, :rounded rounded}
end
end
end
flattening out ))))) introduced by let solves half the problem with reading sexprs
clojure proper could do it too, (defn foo [] (let a 1, b 2) (+ a b)) is very natural
e/defn TwoClocks():
e/client:
let s := e/server(e/System-time-ms())
let c := e/client(e/System-time-ms())
dom/div(dom/text("server time: ", s))
dom/div(dom/text("client time: ", c))
dom/div(dom/text("skew: ", s - c))
end
endMakes sense. I will take a look.
Hereβs a quick experiment (using Joyride) to get a feel for what could be a nice Calva experience, when showing some code to someone not used to parens.
That one only watches files saved on disk, but we could watch the buffer instead to make it more easy to follow as the file is edited.
is it possible to make the syntax match exactly javascript so that javascript editors will have a chance?
Now works in bb too! https://clojurians.slack.com/archives/C06MAR553/p1772800823641239
Ha! That was my next question, @borkdude! π So then maybe also in JS SCI?
This uses babashka pods so that won't work with JS SCI
Iβll try out and see where it may run into problems.
I mean with SCI you could bundle pre-compiled libraries all you want, if you own the project
scittle doesn't support it from source, I'm sure
Which I do. Joyride is my use case right now.
yeah ok
@dustingetz let flattening is in 0.1.12 . I also thought about getting it closer to JS, but the {} are tricky to handle.@pez I added a textmate grammar and some VSCode instructions, but we can rearrange this with Calva, too. https://github.com/replikativ/superficie?tab=readme-ov-file#syntax-highlighting Once we agree on the syntax sufficiently well I would also add it to linguist or do whatever necessary to make sure it works in GitHub's Markdown. Happy to take suggestions.
@whilo, very cool. I now think best would be do this as a separate extension to VS Code. I have made a template project to make it easy to create VS Code extension with ClojureScript: https://github.com/PEZ/vscode-extension-template
i like it'
what does UI code look like
Added a hiccup example at the end https://replikativ.github.io/superficie/examples/side-by-side.html, maybe not ideal (?). Would you prefer a functional representation like (dom/div ...) ? This will basically create a nested function call structure. (Also syntax highlighting is not yet fully correct).
(commata are also whitespace and can be removed)
Anyone (like me) reading on their phone and not getting it: Tilt the phone and read in landscape. π
what a clever idea!
How much work to make it CLJC, @whilo? Then we could use it as a library from Calva and build a live Superficie-to-the-side viewer.
This is very cool, @whilo. Nice work! Have you seen these? They're different from Superficie, but share a similar mentality. β’ Clojure without parens: https://github.com/ilevd/cwp β’ Rhombus (Racket dialect): https://rhombus-lang.org/ I'm quite interested in this direction as an alternative syntax people write for Clojure. The main challenge, aside from an optional alternative lexer, is how to represent macro output in this form while not breaking existing macros. This isn't strictly necessary, if we just accept that macro writers need to understand the underlying s-expr. If we start writing in this form, we would need to adapt our tools: β’ Slurping and barfing β’ Eval word/surrounding form (nREPL clients) β’ Syntax modes for Pythonic Clojure, for automatic indentation β’ clj-kondo? It's possible that the tools continue to work on the CLJ version, even if the source is in the Pythonic version (we need a name for this format, like hiccup). However, that might lead to disconnects in line/col info for error reporting. What're your thoughts on how we can take this all the way? Not just for rendering, but for authoring. Enable the Python users to write Clojure. I think it's an important step for Clojure and it's one I have been meaning to tackle once jank is more stable, but I love that you've already built something which works. It's important because one of the biggest reasons mainstream devs scoff at Clojure is because of the parens. We don't have to like it or agree with it for that to be true. But is Clojure limited by its parens as much as it's enabled by them? Perhaps not, if the same semantics can so readily be described using indentation.
Hey @jeaye! I talked to Matt Flatt before he did Rhombus, I was aware of that, but not of cwp, thank you for pointing me to it.
I am not sure about macros without parens, this seems to be the most challenging bit in Rhombus and I bet it is less obvious than Clojure's how to write them. I think at that point the parens pay off.
I still think it would be good if people would learn to write code with the parens and did not want to go down the road of Dylan, M-expressions etc., which all did not work out. That is why I opted for it to be a bidirectional renderer instead of a language to write in. In your experience, would people still not be willing to learn it then? Maybe not. Because of the indentation cwp looks cleaner than superficie maybe, but I also still need to figure out what it is doing right now, I think the let bindings and assignments with := in superficie are actually better.
I am interested, and heavily invested, in the idea of selling Clojure to existing native developers (C and C++, mainly). Not requiring them to re-learn how to edit text would be an enormous mechanical win, for the learning curve. Also, appealing to mainstream Pythonic aesthetics is not to be undervalued. In fact, it could be one of Clojure's biggest strengths. Yes, a lot of s-expr fans will curdle at the thought, but I place more value in Clojure succeeding than in pure philosophical roots. After all, it's meant to be a practical lisp. Wouldn't the most practical lisp meet people where they are?
Right, Julia kind of did this. This is also why I opted for its syntax with blocks delimited by end. I agree, but I think also that there are genuine trade-offs between familiarity and optimal syntax. There is a reason that Lisp-like syntax is used internally by many systems to express code, e.g. inside of gcc passes. Lisp has a meta-syntax that first can leverage a trivial parser that is available (or easy to code) in any environment by parsing lists. Inside this parsed s-exp language then special forms get a meaning and the grammar definition is much easier through macros.
If you compare Clojure's reader to any non-Lisp language and the complexity of a parser with its grammar (including superficie) then the cost of inlining this list meta-syntax into a specific form becomes obvious. You throw away a lot of structure to be able to have shorthands for the lower-level special forms and grammar. This is at least how I think about it and why I think there is an irreducible loss, even in Julia (because its parser is still non-trivial in comparison to Clojure's).
Having said all this if you can provide a bidirectional mapping (like superficie almost does), then it could be acceptable to write in this language first and maybe even ship some code. I just think there is a high risk this is a slippery slope towards accommodating Python more than winning people over to Lisp. I agree with your goals and am more than happy to keep discussing this though. I also agree that this would be the biggest unblocking for Clojure's (or Lisps) success.
Yep, great points. Lisp is used internally, as you said, which makes a lot of sense. But Clojure devs use lisp more externally, which then hinders its adoption. I am a big fan of "Why not both?". Previously, you could have native, or you could have Clojure. Why not both? Previously, you could have Python-like syntax, or you could have Clojure. Why not both? Figuring out the details of bridging two worlds is not easy. But, for the native case, it's definitely doable. I suspect, with great optimism, that for the Pythonic syntax case, it's also definitely doable. This is where the tooling comes in. s-exprs are great for working with your code's parse tree. But if Superficie can map (nearly) 1:1, then why can't we have the same tooling behavior for that syntax? We should be able to slurp, barf, wrap, unwrap, etc. all using the Pythonic syntax. If we can do this, and we can eval forms with a REPL client, then we can have everything we love from Clojure in a different syntax.
> I just think there is a high risk this is a slippery slope towards accommodating Python more than winning people over to Lisp. Fair consideration. Clojure needs to continue to be Clojure.
Superficie has a s-exp fallback where it passes through macros in Lisp form for instance. So you will have to be willing to learn Clojure eventually. At the moment it is also doing this for reader conditioned forms (can be solved probably) and some other edge cases.
Yeah. It would be needed for macro writing, too.
So, again, like GCC, we can use s-exprs internally. We don't need to hide that.
Do you know why cwp did not take off/stopped?
But the bulk of Clojure code written by these new Clojure devs could be Pythonic. And we could even hook this transformer into documentation websites, so you get to choose which syntax you want to see.
Yes, the dual documentation is definitely also my goal. I want my projects to be generally accessible and not immediately lose people with the parens on my websites/READMEs etc.
No, I don't have any details on CWP. I suspect that part of the reason it didn't take off is because it didn't have enough weight behind it at the correct time, but there may also be some technical reasons with its approach.
One thing I would point out is that LLMs have become very good at writing decent code and I write a lot less code directly these days, which in my mind should also make it a lot easier to learn Clojure through that angle. Not everybody will want to work like this, but the trend will significantly amplify over the near term.
I think that CWP keeps a manual list of fns to make infix, which is a limiting factor.
Hm, it's programmable: https://github.com/ilevd/cwp/blob/main/doc/syntax-and-transpiling.md#custom-constructs
I have built Julia's typed multiple dispatch and optionally also compile a subset of Clojure to OpenCL now directly, including a large scientific/deep learning standard library, games etc. that matches/beats sota native code in benchmarks (Julia's ODE solvers, jax deep learning code). I could build this compiler with Claude (I still have to clean it up though, which is a chore with LLM code, but I could not have typed nearly as fast and tried out all the compilation APIs and figure out all the details etc. in two weeks). I think it is still valuable to be able to show readable code, but it might be less pressing to let people program in it directly. This is just a guess from how things are going.
This is a tangent to this topic, but a reason for me to build superficie, because I still need to show the code (I tested rendering also my special typed dispatch forms with it, and it works ok).
I know this is a heated topic, but I think it is important to very seriously reflect on it these days.
With or without LLMs, people still have aesthetic prejudices. I think this is still a very useful road to travel.
I completely agree.
I am just saying that this eases the necessity to primarily work in cwp/superficie, to being to show the code in its syntax while letting the LLM do the editing in Clojure.
It is still desirable to get a clean complete mapping that can be used for programming though, just less pressing.
This was just not possible in the past.
The work you're doing on Superficie, including that last 2%, can lead us to a light specification for how to map Clojure to/from Pythonic Clojure (still need a name for this). With this specification, we can then have various tool and dialect authors decide whether or not they want to support this, with your implementation acting as a tool, library, and reference for those who want it. We can then figure out what the file extension difference should be, how to handle this on documentation websites, etc. I can say that for jank, I am very interested in supporting this syntax and even leading with it. Is this a direction you're interested in going? If not, that's ok! Don't let me hijack your fun. I will end up going there myself later.
I am interested in getting Clojure out of its corner, because it also limits what I will be able to do, in particular in the direction of scientific computing/research, even if LLMs write the code. Maybe cwp is better than superficie, I need to take a closer look, it is true that Python is just the gold standard for people's readability in general (although I know mathematicians that did not get it and preferred Julia).
I just don't want to have to build an insane amount of parser infrastructure, tooling etc., but then again LLMs make this considerably easier (even if still somewhat messy, but Clojure helps a lot in reducing the messiness).
@ilevd tagging you here if you are around π
@borkdude Let me know if you have thoughts on this. If it's something we can get behind together, I'm confident we can make it happen.
@jeaye also happy to discuss the multiple dispatch work, I will move this to private to not spam it here.
I've made #noparens to discuss this (name credit goes to Christian). All are welcome to join.