Top
Best
New

Posted by yacin 10 hours ago

Why Janet? (2023)(ianthehenry.com)
389 points | 195 commentspage 4
bjourne 6 hours ago|
Damn it, Janet. No proper namespaces. Hard pass.
xigoi 32 minutes ago|
What do you mean? When you import a module in Janet, it adds a namespace prefix to all symbols. What more do you want?
veqq 3 hours ago||
> 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

xuzhenpeng 8 hours ago||
[flagged]
rohitsriram 8 hours ago||
[flagged]
nicechianti 6 hours ago||
[dead]
shevy-java 9 hours ago||

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

xigoi 9 hours ago||
What would be a better syntax according to you? I have found Janet’s syntax very pleasant to work with as opposed to JavaScript, Lua or even Python.
Imustaskforhelp 9 hours ago||
can't there theoretically be a language which transpiles to Janet to get all the benefits without additional paranthesis too?

Not sure if such transpilation would have a perf hit though, I hope somebody responds who knows about it more.

I don't deny that syntax matters itself too but there are some ideas of janet like sandboxing and other features which seem to me to be worth implementing in other languages too.

Personally, I would be really interested in a language like lua/wren which can transpile to Janet too.

rmunn 6 hours ago|||
"... all the benefits without additional parenthesis too?"

I guess you don't like Lisp's syntax. I didn't either until I realized the key insight: when you're writing Lisp, you're basically writing an AST. Which is why it's so easy to manipulate your code. Want a new feature the language doesn't have, such as the pattern-matching they added to C# a few versions back? You can add it yourself; you don't need to wait for a language committee to implement it years after you needed it. That's all that macros are: functions that take AST and return AST, which is then executed.

And once I realized that Lisp's syntax was basically an AST, I no longer saw the parentheses. Now I just see blonde, brunette, redhead... Oops. Sorry. Wrong reference.

iLemming 24 minutes ago||||
> like lua/wren which can transpile to Janet too.

Really? I've used dozens of languages and honestly, I just can't wrap my head around how ugly Lua code can get. At first, I tried treating it as "javascript with no bad parts", turns out, modern JS is far, far better than 1996 JS and nicer than Lua. The most annoying part about Lua is that I never know how to format it for better readability - should I add line breaks, or not, etc. lua-fmt often just makes it worse.

When I found Fennel I immediately moved to it, even though it was "experimental". Since then, I just don't want to deal with Lua, aside from some small one-liners.

petee 9 hours ago|||
I guess you could transpile direct to Janet bytecode, and performance would be in theory the same as native Janet?
flintenmuschi 7 hours ago||
Why, Henry?
wolfi1 9 hours ago|
why is it called Janet? perhaps to prevent it to be identified with the acronym for Lots of Irritating Single Parenthesis?
petee 9 hours ago||
It was named after the sentient computer system in the TV show "The Good Place"

A humourous clip: https://youtu.be/etJ6RmMPGko?si=W98LdG1jDdUCXsHV

embedding-shape 9 hours ago||
Also mentioned in the GitHub repository:

> Why is it called "Janet"? Janet is named after the almost omniscient and friendly artificial being in The Good Place

https://github.com/janet-lang/janet#why-is-it-called-janet

xigoi 9 hours ago|||
If it was called [Something] Lisp, Lisp enthusiasts would complain that it’s not a lisp because it does not use linked lists as the primary data structure.
Imustaskforhelp 9 hours ago||
I know that Lisp has lots of paranthesis and I don't have enough experience with Lisp at all.

But from the looks of it, Janet has some great ideas like the one that @ramblurr shared here about sandboxing ("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.")

Lisp from my understanding is incredibly polarizing and many people love it and many people hate it and that's fine, but at a certain point wouldn't it feel repetitive for statement like this and I am unsure of how healthy discussion about programming concepts can be done this way.

There are so many interesting things from lisp-y languages like Janet and Julia is technically lisp-y too and Julia's compilation to GPU is awesome and Nim too which can compile to C/C++/JS!

It's just so many interesting concepts overall in programming that paranthesis don't seem a concern to me as the underlying concept can be translated to something else, like sandboxing feature, transpilation to GPU or multiple targets!

And there are many unique concepts in non-lispy languages like golang (cross-compat, portability with static binaries), elixir (concurrency!) too.

It's just good to see the amount of innovation within programming from all spheres of influence :-D

iLemming 36 minutes ago|||
> Lisp from my understanding is incredibly polarizing

No it's not! It's as "polarizing" as "group theory" or "set theory". Lisp is fucking math - it maps closely to formal mathematical/logical notation. You just can't "hate" math - you can be confused by it, be unfamiliar with it, intimidated by it. But hatred directed at something that is simply precise and consistent says more about the person than the thing.

adrian_b 9 hours ago|||
While I do not like the excess of parentheses of LISP and similar languages, their syntax is very consistent and predictable. Moreover, while LISP has an excess of parentheses, it omits a greater number of commas that are required in many other programming languages.

I am much more annoyed by the random syntax inconsistencies of most popular programming languages, which are either caused by original language design mistakes, or, more frequently, by the late addition of some features that were not planned in the original language, so they had to be squeezed in with the help of various ugly workarounds.

While during the last years I have not used much LISP like languages, there have been times when I used them a lot, for several years, in scripting applications, e.g. the LISP variant of old AutoCAD, the Scheme-like scripting language of the Cadence EDA applications, or the scsh Scheme dialect that is usable for replacing UNIX shell scripts.

In all cases, these languages allowed a greater productivity associated with rarer bugs than the more popular scripting languages, like Python, Perl, TCL, bash.

While aesthetically I might prefer the look of a Python program, for solving a practical production problem I would prefer to write scripts in one of the LISP derivatives. Obviously, the productivity in various programming languages depends a lot on individual preferences and previous experiences.

It should be noted by all those who believe that the LISP-derived languages have too many parentheses, that the C programming language and all languages with syntax derived from it, like Java or Rust, have a great excess of parentheses in comparison with the older languages that had better designed syntaxes, e.g. ALGOL 68 or IBM PL/I.

For example, compare

  for (i = 1; i <= 100; i += 5) { ... }
with

  for i from 1 to 100 by 5 do ... od
or

  if ( ... ) { ... } else { ... }
with

  if ... then ... else ... fi
The first example has 12 syntactic tokens instead of the minimum required, which is 6.

The second example has 8 syntactic tokens instead of the minimum required, which is 4.

If I cannot have a decent programming language with a minimum number of parentheses, I would rather have a programming language where all the places that need parentheses are predictable, like in LISP, instead of having a language like C and its derivatives, which require parentheses in random places, for no good reason at all.

NetMageSCW 5 hours ago||
Now do

    for (i = 1; i <= 128; i *= 2) { … }
with by.

Now do

    if (x <= 0)
        throw ParameterException;
with fewer “syntactic tokens”.