The compilation strategy is very much what Carmack did in Trinity and whether you got your inspiration there or independently had a great idea, that's good company to be keeping.
Keep it up!
however my only gripe with clojure while it's easy to write and comprehend at first - it's difficult to read. & yet most our time we read code not write it. but then again it might be my lack of brain power.
Once Clojure parity is achieved, I'm interested in static typing, pattern matching, value-based errors, and some other opt-in improvements that I think will greatly improve both readability and toolability (i.e. how well tooling can work with the code, based on what it knows of the code). Stay tuned. :)
For a practical example of a Clojure-like language with a completely static type system (with affine typing), see Carp. https://github.com/carp-lang/Carp
I don't see why there can't be a Carp mode in jank, with bridges in place to connect the Clojurey world from the Carpy world. This would allow jank users to develop interactively to start with, figure out their shapes, use the REPL, etc. Then, if they want, they can lock down some parts of the code for both performance and correctness gains.
I have to wonder if "Maybe Not" is similar, since he's not actually an expert in types, either afaik.
Alexis King wrote a partial rebuttal to Maybe Not: https://lexi-lambda.github.io/blog/2020/01/19/no-dynamic-typ...
If your argument is you are an expert but Hickey is not, criticising him on his language design skills seems like a logical mistake. He's one of the foremost language designers of the current era. "Maybe Not" is a speech by an expert talking in his field of expertise.
If your argument is that his confidence is unfounded, again, he's an expert talking in his area. He can reasonably take a confident attitude in that, even if he has unfounded confidence in other fields he isn't an expert in. Lots of experts do that, it is a well founded stereotype of smart people.
He doesn't need to be an expert in implementing types to judge whether they are a good language feature.
To me, the really important idea wasn't a criticism of static types in general.
Instead it was the idea that static typing in most (all?) mainstream implementations conflates concepts that should be separate, specifically the shape of the information that we have (e.g. what fields of what types), and whether a particular bit of information is available and required (e.g. nullability).
He contends that the former belongs in our usual "type definition", whereas the latter relates instead to a given context. For example, my PassportForm type always has a date-of-birth field in its _shape_, but whether it's statically required/present to exist depends on whether we're at a HTTP API boundary, an internal domain function boundary, writing into a database.
It sounded like that kind of "nullability masking" was intended as a feature of Spec, but I don't get the impression it was ever implemented.
Thanks for jank! It’s great to be reading about it, listening to you talking about it at conferences, and I can’t wait to try it out!
Also writing Clojure can be incredibly terse, resulting in quite high-effort when reading. Conversely, a lot of time I can condense hundreds of lines of equivalent python into 5 or 6 lines of Clojure. Having all of this functionality condensed into something you can fit in a tweet really helps for grokking larger parts of the dataflow or even larger system. So there are tradeoffs
Plus structural editing and the repl really help with the “reading” experience (reading in quotes because it’s much more interactive than reading)
I'm curious if you have any example of this? Even if it's an hyperbole, I don't really see how.
1. You’re mapping or reducing some dataset
2. Your iteration logic does not branch a lot
3. You can express your transformation logic using higher order functions (e.g. mapping a reduction operation across a multidimensional array)
Some domains have a log of this style of work—finance comes to mind—others do not. I suspect this is why I’ve personally seen a lot more of Clojure in finance circles than I have in other industries.
The 1:20+ is definitely not hyperbole though. Using transducers to stream lazy reductions of nested sequences; using case, cond-> and condp->; anywhere where you can lean on the clojure.core library. I don’t know how to give specific examples without giving a whole blog post of context, but 4 or 5 examples from the past year spring to mind.
It’s also often the case that optimizing my clojure code results in a significant reduction of lines of code, whereas optimizing Python code always resulted in an explosion of LoC
Personally I find Python particularly egregious. No map/filter/reduce, black formatting, no safe nested property access. File length was genuinely one of the reasons I stopped using it. The ratio would not be so high with some languages, ie JavaScript
Even with Elixir though, many solutions require 5-10 times the amount of lines for the same thing thing in Clojure. I just converted two functions yesterday that were 6 & 12 lines respectively in Clojure, and they are both 2 pages in Elixir (and would have been much longer in Python)
Usually these are problems where you need to run along a list and check neighboring elements. You can use amap or map-indexed but it's just not ergonomic or Clojure-y (vs for instance the imperative C++ iterator model)
The best short example I can think of is Fibbonacci
https://4clojure.oxal.org/#/problem/26/solutions
I find all the solutions hard to read. They're all ugly. Their performance characteristics are hard to know at a glance
If you come across a post or an example that shows those differences, I would be very interested!
(defn report [date]
(let [[d w m q y] (-> (comp tier* recall* (partial c/shift date :day))
(map [1 7 30 90 365]))]
(reduce (fn [memo {:keys [card code]}]
(cond-> memo
true (update code (fnil update [0 0 0 0 0 0 0 0 0 0]) (q card) inc)
(<= 4 (d card)) (update-in [code 6] inc)
(<= 4 (w card)) (update-in [code 7] inc)
(<= 4 (m card)) (update-in [code 8] inc)
(<= 4 (y card)) (update-in [code 9] inc)))
{}
(k/index :intels)))))
The elixir code I was able to condense down into: def report(facets, intels, day) do
[d, w, m, q, y] = for x <- [1, 7, 30, 90, 365], do: Date.shift(day, day: x)
Enum.reduce(intels, %{}, fn intel, acc ->
facet = Map.get(facets, intel.uuid, :zero)
[q0, q1, q2, q3, q4, q5, d4, w4, m4, y4] =
acc[intel.code] || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
quarterly_tier = tier(facet, q)
Map.put(acc, intel.code, [
if(quarterly_tier == 0, do: q0 + 1, else: q0),
if(quarterly_tier == 1, do: q1 + 1, else: q1),
if(quarterly_tier == 2, do: q2 + 1, else: q2),
if(quarterly_tier == 3, do: q3 + 1, else: q3),
if(quarterly_tier == 4, do: q4 + 1, else: q4),
if(quarterly_tier == 5, do: q5 + 1, else: q5),
if(tier(facet, d) >= 4, do: d4 + 1, else: d4),
if(tier(facet, w) >= 4, do: w4 + 1, else: w4),
if(tier(facet, m) >= 4, do: m4 + 1, else: m4),
if(tier(facet, y) >= 4, do: y4 + 1, else: y4),
])
end)
end
It was much longer prior to writing this comment (I originally used multiple arity helper functions), but it was only fair I tried my best to get the elixir version as concise as possible before sharing. Still 2x the lines of effective code, substantially more verbose imho, and required dedicated (minor) golfing to get it this far.Replacing this report function (12 lines) + one other function (6 lines) + execution code (18 lines) is now spread across 3 modules in Elixir, each over 100 lines. It's not entirely apples to oranges, but trying to provide as much context as possible.
This is all just to say that the high effort in reading it is normally a result of information density, not complexity or syntax. There are real advantages to being able to see your entire problem space on a single page.
While jank is technically a negative term, its quite playful as opposed to scathing. My favourite usage was in MTG where large control decks that just slap together strong cards are referred to as "jank piles".
Project manager fires the entire team except 1 intern to finish the project with 1000 points of stories in 1 sprint? Heh or did you just figure out jank wasn’t capable of doing the job what did you expect?
Hotfix to fix a bug with the stage environment because the SREs set it up wrong? No bro it’s jank it’s that jank thing. Source: ctrl F “jank” in the message analytics and copilot says all matches are in the stage environment and that jank is also a tech thing. It also bright up every engineers profile that lists jank as a skill. Time to pick a scape goat.
Do you plan to make Windows support first-class? I think a lot of people looking at LLVM based languages are interested in alternatives to C++ for games.
jank is Clojure. However, the Clojure name is trademarked and using it requires permission which I don't have. Furthermore, I want to build upon the Clojure base to provide more, going forward. That may include static typing, value-based error handling, first class pattern matching, and so on. Those would be opt-in features on top of Clojure. All of these reasons lead me to not use Clojure in the name (like Clojure++, ClojureNative, etc).
> Do you plan to make Windows support first-class? I think a lot of people looking at LLVM based languages are interested in alternatives to C++ for games.
Indeed, a lot of game dev folks use Windows. Right now, jank's Windows support is limited. My initial audience is Clojure devs who want native access and lighter binaries. Once that launch has stabilized, I will focus on appealing to existing native devs who want to embed an interactive, functional language into their C++ applications. That will requires strengthening the Windows support, establishing stable native APIs, and writing the onboarding material for lisp, REPL-based editing, data-driven design, and so on. This is a much larger task, which is why I'm focusing on existing Clojure devs first.
Ideally without controlling the code of the main application (e.g. to implement a plug-in).
In short, jank is Clojure, but it's on LLVM and has seamless C++ interop. You still get full nREPL capabilities, can redefine anything on the fly, and we can actually JIT compile C++ code alongside your Clojure. The seamless C++ interop is first of its kind, for a lisp, and is done by JIT compiling C++ alongside the LLVM IR we generate for jank and then stitching them together into one IR module.
Note, jank isn't released yet. I'm targeting the end of this year for the first alpha release. I put out monthly development updates on the jank blog, with the next one coming out this week.