Why Janet? (2023)

344 points167 comments8 hours ago
krinne

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.

show comments
janetacarr

I have my qualms with Janet. Mostly, it's lack of package management versioning and lack of libraries in general (advanced HTTP routing, etc).

I do LOVE that Janet can create binaries with JPM, scripts, and is very portable. I once put the Janet programming language on the Playdate game console as POC.

I actually do enjoy writing Janet, but every time I do people think I created the language (I did not).

show comments
1313ed01

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/

show comments
ramblurr

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

show comments
xrd

The author made these using Janet (discussed on HN in the past):

https://bauble.studio

https://toodle.studio

Those two fascinating art tools got me very excited about Janet a while back.

a-french-anon

> 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.

show comments
mackeye

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.

soomtong

This document was really helpful when I first met Janet:

https://janetdocs.org/tutorials

https://janet.guide/ (the author's one)

show comments
didibus

Wow I never realized Janet was released more than 10 years after Clojure.

Clojure: 2007

Janet: 2019

uka

> 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.

show comments
wodenokoto

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.

show comments
0x0203

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.

show comments
1vuio0pswjnm7

The author does not mention that Janet comes with _built-in_ networking

Having tried many tiny interpreters over the years, that's relatively rare IME

skeledrew

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/

show comments
AHTERIX5000

Does embedding Janet still lean on global state?

show comments
zabzonk

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

defrost

Previously (April 2023) | 140 comments: https://news.ycombinator.com/item?id=35539255

lindig

> 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.

show comments
6LLvveMx2koXfwn

Maybe needs a (2023) in the title?

hlude

The shell DSL is what made me want to try Janet

show comments
gspr

The embeddability sounds very appealing. Does anyone have experience with using this somewhere one might traditionally reach for Lua?

show comments
boltzmann64

if those are the reason why you love janet, then you will love tcl because you will be able to do all the same things without drowning in parenthesis and weird syntax.

show comments
IshKebab

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.

show comments
netbioserror

Janet is ALMOST an incredible tool...but what I want is a very clear bifurcation between the standard library's stateful mutating procedures, and stateless value-returning functions. I ran into that wall hard trying to make something non-trivial.

It also turns out that the mix is due to the standard library leaning on raw C loop iterations underneath whenever it can. Which is great! But it confuses the library's interface paradigms.

anthk

Luxferre.top has some Janet based softwrae.

veqq

> I never thought it could happen to me.

But I am truly biased. I have basically forgotten how to code everything else (besides APL family languages) in the past _checks notes_ 10 months since I started Janet. I even run a community [docs site](https://janetdocs.org/) and am writing [my own tutorial](https://janetdocs.org/tutorials/learn-to-program) (albeit slowly). I even use it in production for all new software (within 3 weeks of starting, I had rewritten all personal scripts etc.)

> Janet is simple

You can do literally everything with just hashmaps. The whole language is basically a hashmap, implementation wise. `(keys (curenv))` prints out all locally defined symbols. `(keys (getproto (curenv)))` prints the parent hashmap of the current environment i.e. all the core symbols. I don't, but you can basically do CLOS via hashmaps (and there is a [fuller implementation](https://git.sr.ht/~subsetpark/fugue) too.)

> Janet is distributable

I have like 20 websites and another dozen or so services running on Janet (with the [Joy webframework](https://github.com/joy-framework/joy) which I wrote a [tutorial](https://janetdocs.org/tutorials/Joy-Web-Framework) for), on a single free-tier VPS with 512mb of RAM.

> Janet has ... immutable collections

...not really. In reality, the whole standard library constantly returns mutable versions from everything. There's no reason to really try to be immutable at this point. Although there are cool [combinator libraries](https://git.sr.ht/~subsetpark/apcl-janet) and I've even made combinatorish versions of basic functions:

    (defn better-cond
      [& pairs]
      (fn :bc [& arg] # names for stack traces
        (label result
               (defn argy [f] (if (> (length arg) 0) (apply f arg) (f arg))) # naming is hard
               (each [pred body] (partition 2 pairs)
                 (when (argy pred)
                   (return result (if (function? body)
                                    (argy body) # calls body on args
                                    body)))))))
Combinatory inspired cond, which allows for pairs. The test does not need an argument and the body may be a simple value or a function:

    (map (better-cond
             string? "not a number"
             odd? "odd"
             even? "even") 
      [1 2 3 "cat"]) # the args!
    (map
        (better-cond
         1 (fn [arr] (array (min ;arr) (max ;arr)))) # (recombine array (unapply min) (unapply max)))
        (partition 2 (range 10))) # these are the args!
    ((better-cond
          < "first is smaller"
          > "second is smaller")
         5 3) # these are the args passed into the func! I am excited!
> Janet lets you pass values from compile-time to run-time

That's what got me hooked, in a few ways. In Racket or Go, I had to do a lot of work to process data at compile time so the runtime could literally just be a lookup table. In Janet? That's the default behavior of any `def` outside of main. The following turns a .tsv of the bible into a hashmap in the binary, when compiling:

    (def verses  (reduce (fn [acc line]
                           (let [parts (string/split "\t" line)]
                             (if (= (length parts) 5)
                               (let [[_ abbrev ch vs text] parts]
                                 (put-in acc [abbrev ch vs] text))
                               acc)))
                         @{}
                         (string/split "\n" (slurp "kjv.tsv"))))
    
    (def abbrev-array (keys verses)) # also makes an array of the abbreviation column
So the rest of the program is literally just accessing the hashmap ([twice as fast](https://codeberg.org/veqq/verse-reader#performance) as the Golang version using `embed`):

    (defn main [_ & args]
      (if (or (empty? args) (= "-h" ;args) (= "help" ;args))
        (do (print "Usage: kjv <book> [chapter:verse]") (os/exit 1))) # show help
      (let
       [Capitalized (string (string/ascii-upper (string/slice (first args) 0 1)) (string/slice (first args) 1))
        book        (find |(string/has-prefix? $ Capitalized) abbrev-array)]
    
        (pp (match args
              [_ chap verse] (get-in verses [book chap verse])
              [_ unsure]     (match (string/split ":" unsure)
                               [chap verse] (get-in verses [book chap verse])
                               [chap]       (get-in verses [book chap]))
              [_]            (verses book)))))
The equivalent go program was 5x longer and required an extra program to convert data into a 40k line .go file with a giant literal hashmap, to be faster than the naive Janet.

...but actually Ian Henry means Janet e.g. keeps closures synced across images/sessions:

    (defn timer [t] 
      (var t t) # this is slightly annoying, must shadow as params are immutable
       [(fn [] (set t (+ t 1)))
       (fn [] (set t (+ t 2)))])
    
    (def tx (timer 0))
    # call like this:
    ((tx 0))
    ((tx 1))
    
    # make an image and save it to file
    (def my-module @{:public true})
    (spit "test.jimage" (make-image (curenv)))
Exit and start a new REPL session:

    (defn restore-image [image]
      (loop [[k v] :pairs image]
        (put (curenv) k v)))
    
    (restore-image (load-image (slurp "test.jimage")))

    ((tx 0))
It saved the closure and all relevant image in the `(curenv)` hashmap.

Condensed from my longer response: https://lobste.rs/s/y0euno/why_janet_2023#c_lspe6n

bjourne

Damn it, Janet. No proper namespaces. Hard pass.

makach

Excellent. Although I suspect the author of the programming language invented this Janet for all the perfect puns. Yes, Janet. No. Janet.

shevy-java

    (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.

show comments
flintenmuschi

Why, Henry?

wolfi1

why is it called Janet? perhaps to prevent it to be identified with the acronym for Lots of Irritating Single Parenthesis?

show comments