71 comments

[ 0.26 ms ] story [ 89.7 ms ] thread
Pretty compelling, especially "Janet does not adhere to the ancient customs. CAR is called first. PROGN is called do. LAMBDA is fn, and SETQ is def." - a sign of good sense for sure!

How fast is it?

Also my main objection to Lisps is still the horrible bracket syntax. Yes it's unambiguous and easy to parse, but it's HORRIBLE to read and edit. I wish this project had been a success (or something similar to it): https://readable.sourceforge.io/

Also I don't think static typing is really optional for me at this point.

It's a scripting language, so it's not going to compete with anything compiled or JITed, but it has a pretty efficient threaded bytecode interpreter (that is almost more interesting than the language itself!). It's certainly good enough for most situations where you would reach for a scripting language.
This post is refreshing - smells of the pre AI discussions on the internet. A new language, a new syntax, heavy debate with people who have spent years writing code. I think someone should start a community online where AI isnt allowed.
Ironically, the top comment (this) is now about AI.
(comment deleted)
> But by allowing you to unquote literal functions, Janet makes it possible to write macros that are completely referentially transparent.

These lisp guys really get excited over very abstract things. If you say this to an average person on the street they will probably try to run away.

> over very abstract things.

I beg to differ. There's just isn't "easy and straightforward" path to simplicity. We thought that explaining the world with "objects" was simple and instead of using already existing language, OOP took "objects" (an easy choice) and invented a elaborate taxonomy of "patterns" to work around the limitations of objects. Just look at this mess:

- Strategy Pattern: Interface + multiple classes + dependency injection + factory maybe. Bruh, it's just a function that takes a function.

- Singleton: Private constructor + static instance + thread safety + double-checked locking. Bruh, it's a fucking value. You define it once. It doesn't change. You're done.

- Observer/Event System: Interface + listener registration + event loop + memory leak when you forget to unsubscribe. Bruh, tis a fucking function applied to a list (or stream).

- Decorator; Wrap a class in another class that implements the same interface. Bruh - it's function composition. You learned this in algebra class before you turned fourteen.

- Command: Encapsulate a method call as an object with execute(), undo(), history queue... It's a function stored in a variable. That's it. That's the pattern.

- Factory: Separate class whose entire job is to call constructors. Come on, it's just a fucking function.

- Template Method: Abstract base class with a method that calls abstract methods subclasses must override. It's a higher-order function.

- Iterator: Interface with hasNext() and next(), mutable state, ConcurrentModificationException. It's fucking map.

The Gang of Four book exists because Java made functions second-class citizens, so programmers spent 20 years building elaborate object scaffolding to simulate... functions. FP didn't solve these problems. It just never had them.

Yet somehow the industry likes to pretend that every programmer knows (or should know) OOP, while keep telling everyone how hard programming is.

Those who found the truth understand that there's a reason why Lisp just refuses to die and it's unlikely it ever will. At 70 years, it is still flourishing.

I started writing a Scheme interpreter about a year ago and got pretty far. I dropped it a few months back since I got a new job.

I'm thinking of getting back and am wondering if the niche (and difficult for me to implement) features are worth it. I might be better off skipping dynamic-unwind, maybe even ripping out call/cc, in favor working on the debugability, ecosystem, performance, and package management story.

> If you say this to an average person on the street they will probably try to run away.

This is the average reaction I get any time I get the "so what do you do" question. I try to stay very vague "I do computer work" or something. Or I'll say "Oh, nothing interesting" and try to change the subject. Any more specific than that and they start looking for the exits.

This got me thinking of Hy. I wonder how syntactically close they are; there might be an exploitable Python -> Hy -> Janet path here.

[0] https://hylang.org/

I use both. They're similar for simple use, but above a certain level of complexity Hy has a lot of Python-isms that bleed through. It really doesn't ever let you forget that underneath all the parentheses you're really writing Python. Janet feels like its own stand-alone language in that respect, where Hy is more like a syntax swap.

I have the impression that Hy's user base is larger, though (not that either one is huge).

The embeddability sounds very appealing. Does anyone have experience with using this somewhere one might traditionally reach for Lua?
> Instead of regular expressions, Janet’s text wrangling is based around parsing expression grammars. Parsing expression grammars are simpler, more powerful, and more predictable than regular expressions.

I would dispute that this is the case. In PEGs, alternatives are not commutative, unlike in regular expressions. This can lead to quite frustrating debugging. While a valid choice, the advantage over REs is overstated.

I'd argue they are not commutive in regexes either, at least as implemented in practice. Implemented regexes favor the leftmost alternative even when both sides of the alternative match. This matters in cases like: capturing groups, and backtracking implementations. There absolutely are cases where one ordering of alternatives could yield catastrophic backtracking for some input, while the other will avoid it completely.

I personally don't like this at all. This means that regex engines that try to generate optimized matching code for an expression can end up generating suboptimal code if you don't want alternative order to matter, since the engine needs to keep that invariant, except in the case when it can prove that the alternatives won't overlap, and a later one can be checked in constant time. If both are true, it is legal to reorder them to do the constant time check before the big complicated wildcard-filled alternative.

But personally, I have never written a regex where I actively cared about the alternative evaluation order. I've used some other people made where order is important but never written one myself.

I'd love to be able to tell the engine "feel free to swap the evaluation order of my alternatives while optimizing", but few if any such engines offer that as a feature.

Now I get that PEGs have commutivity problems are that are different from regexes', which make the issue worse, but that doesn't mean regexes do things right either.

Something I'd observe here: without expressing an opinion about PEGs vs regexes, I'd note that regexes are much more widely used. They're also completely sufficient for an awful lot of text processing tasks. PCRE regexes would be a great inclusion to the stdlib, and would probably be popular.
I've been drawn into the Janet posts that surface every once in a while here on HN, but found the otherwise highly praised "Janet for Mortals", not being for mortals at all.

    (defn foo [first & rest] ...)
So basically Lisp 2.0.

Although, this here is a good idea:

"pass values from compile-time to run-time"

Would be nice if some kind of "scripting" language be as fast as a compiled language, but without ruining the syntax. Just about 99% of the languages that are shown, have a horrible syntax. Syntax is not everything, but most language designers don't understand that syntax also matters. So tons of horrible languages emerge. Nobody will use those languages, so 99% of them will die off quickly.

Excellent. Although I suspect the author of the programming language invented this Janet for all the perfect puns. Yes, Janet. No. Janet.
why is it called Janet? perhaps to prevent it to be identified with the acronym for Lots of Irritating Single Parenthesis?
Seems some of the listed advantages for Janet would also apply for tcl (small/simple, easy to learn, embeddable, usable as a shell, great for domain specific languages). It would be interesting, to me at least, to see a fan of Janet compare the two.
Always nice to see janet getting some attention.

shout out to one modern feature: sandbox

"Disable feature sets to prevent the interpreter from using certain system resources. Once a feature is disabled, there is no way to re-enable it."

https://janet-lang.org/api/misc.html#sandbox

Luxferre.top has some Janet based softwrae.
(comment deleted)
There is also fennel, earlier language originally by same developer, that is similar, but compiles to, and is fully implemented in, Lua. No standard library of its own so missing many nice things like the parser library from janet, but it is good for writing scripts for things that embed Lua.

https://fennel-lang.org/

Does embedding Janet still lean on global state?
My first question too, and I checked out the linked book [1], and sure seems like it! There's global functions like `janet_init()` and `janet_loop()` all over the place.

A language shouldn't advertise itself as "embeddable" if it does this. It means you can't have multiple interpreters, you can't use it on multiple threads, etc. GNU Guile does this too, and it's a baffling decision! For my field (audio plugins like VSTs), it means it's absolutely a no-go, because hosts can load any number of instances of your plugins and potentially run them in parallel in the same address space, they can't rely on global state like this. Each interpreter has to be separate.

Lua does this right, as does Python (as of 3.12, when they made the GIL local to each interpreter) and I think most of the JavaScript engines. And it's not hard, instead of a global `janet_init()`, just have an opaque pointer bundle all the state, like `janet_init(interpreter)`. If you want a global interpreter, just stick it in a global variable.

[1]: https://janet.guide/embedding-janet/

janet has replaced sh, python, awk, etc. for me, for system scripts over a certain length! it has a very fast startup time (on my system, 1.4ms via hyperfine vs. 1ms for dash) for scripts (not compiled executables), and its sh-dsl module allows typing shell commands very elegantly, like ($ cmda w x | cmdb y z). the ability to load an image to debug is a big help, too. i've started using it very recently but it's probably one of my favorite languages now, and the only other lisp i've used is mit scheme for sicp.
> janet has replaced sh, python, awk, etc....

babashka did that for me.

Thought this might be about JANET, the rationale for which I have never really understood. The wikipedia article on it is not very explanatory: https://en.wikipedia.org/wiki/JANET
From memory, it was for Joint Academic Network. I'm surprised the Wikipedia article doesn't mention it at all, but it seems hard to find an authoritative source.
> SETQ is def

At first I said "what" out loud, since SETQ doesn't create bindings, it only updates them then I read the doc (https://janet-lang.org/docs/bindings.html) and the author is indeed wrong ("bindings created with def are immutable"). He probably meant "SETQ is set".

I really want to like Janet, as it seems to be the sweet spot between Guile, Tcl and CL (minus the speed/maturity of SBCL) but I have a visceral reaction to square brackets (so vectors) being used in lambdas and control flow operators. Same as Clojure, I simply can't get over it. Maybe I will with enough effort?

Also, what's the current LSP/SLIME status? Really important these days.

You can... just not use square (and curly) brackets. Instead of `[1 2 3]` just write `(array 1 2 3)`. Instead of `(fn [x] (+ 1 x))` just write `(f (x) (+ 1 x))`. They are never necessary.
> I have a visceral reaction to square brackets

Once you understand how destructuring works in Clojure, it then becomes obvious what role square-brackets play.