top | item 34843306

The Janet Language

299 points| xrd | 3 years ago |janet-lang.org | reply

195 comments

order
[+] ianthehenry|3 years ago|reply
Oh hey! Nice to see this on the front page here. I love Janet -- I've been using it for about year and a half, and it's now my go-to scripting language when I need something more powerful than bash, or when I want to hack on goofy little side project (some examples in my profile).

Parsing expression grammars (think, like, declarative parser combinators?) are a really great feature for ad-hoc text parsing -- and nicer than regular expressions, in my opinion, for anything longer than about ten characters.

The language itself is also a great introductory language-with-lots-of-parentheses, so you can explore compile-time metaprogramming with familiar runtime semantics -- think JavaScript plus value types minus all the wats. The embeddability and easy distribution (compiles to a static binary on any platform) is a huge plus as well.

Honestly I like Janet so much that I'm writing a book about it, because I think it's a shame that the language isn't more well-known. It's great! You should check it out right now!

[+] weavie|3 years ago|reply
Is there somewhere I can keep up to date with books progress?
[+] mike_hock|3 years ago|reply
I went there, saw the Lisp syntax, and noped back out.
[+] pmoriarty|3 years ago|reply
I love Lisp (particularly Scheme), and heard that Janet was strongly inspired by Lisp and is "really Lisp underneath", but is really fast and strong at math, so I thought I'd give it a try, and after learning it I started wondering to myself, "why am I not simply using Lisp?"

Though arguably lispy, Janet just wasn't lispy enough for me. It was missing the simple, elegant sexp syntax I dearly loved, and I started to wonder what huge win I was getting from using it instead of just using Lisp or Scheme? Having not found a good answer, I did just that.

That was the last time I bothered trying to learn a "Lisp-like" language that wasn't actually a Lisp, and decided to just stick to Lisp/Scheme. They do everything I need, are good at math, and are plenty fast enough for me.

[+] dri_ft|3 years ago|reply
>It was missing the simple, elegant sexp syntax I dearly love

In what sense does Janet not have sexp syntax? Seems plenty sexpy to me. Purists seem to say it's not a lisp because its underlying data structure is not (cons-based) lists as in classical lisp, but I don't see what syntactic difference there is.

[+] joaogui1|3 years ago|reply
Uhm, sorry for asking, but are you sure you're not mistaking Janet and Julia?
[+] progre|3 years ago|reply
I think its a pretty nice language.

Those extra bits of syntax that makes it "not a lisp" are mostly around defining "not list" kind if data structures. I find it practical.

[+] nine_k|3 years ago|reply
The list of features available out if the box is pretty impressive, especially if you are scripting something concurrent. I would tolerate the syntax quirks in exchange for that.

Or maybe your particular Scheme has all of that out of the box, too. Which one do you normally use, if you don't mind?

[+] crabbone|3 years ago|reply
One thing that bothers me about languages that compose functions like this f(g(x)) is that when programming interactively f is typically an afterthought. So, you start out with g(x) and then need to go all the way back and add f. Similarly, when x turns out like it needs to be elaborated, and you either have to go all the way back, or edit the expression in place making it longer and inevitably facing problems with terminal length and terminal input being determined by when you press "Enter".

I like Lisp languages, and would take Scheme over Python for my job in a heartbeat, if I was allowed to. But, I think, that if we want interactive Shell-like programming, we really need to address the issues above in the language. Shell pipes are a good start, but they are quite restricted in what they can do and require xargs "extension". Some languages also have the "where" form, where arguments to a function can be elaborated after the function call.

If I was ever to design a language for interactive programming, I'd certainly try to have these two features in it.

[+] adrianmsmith|3 years ago|reply
> So, you start out with g(x) and then need to go all the way back and add f.

That's one thing I will say after coming from Perl/PHP to Java, is that despite its verbosity and the uselessness of having to write .stream(), I much prefer Java's stream.map(...).filter(...) syntax over the more functional-style filter(map(list, ..), ..) syntax. The Java syntax reads left-to-right, which is the order you want when you're thinking about code, and also as you say writing it. I think if I were creating a programming language I too would try to make stuff read left-to-right as much as possible.

[+] ianthehenry|3 years ago|reply
Thank you for making an actually relevant point about syntax. I agree with this 100%, and I love Janet, and was recently doing a lot of interactive Janet programming for a generative art playground.

So I added postfix function application. So instead of (f (g x)), you can write (g x | f).

I liked the syntax a lot, but it looked really weird with operators: (calculate x | + 1). So I made operators automatically infix: (calculate x + 1).

I also didn't like that the transformation from foo to (foo :something) (record field access) required going back and adding parentheses before the value, so I added foo.something syntax that means the same thing.

The result is something that's very easy for me to type and read:

    (def eyes
      (eye-shapes
      | color (c + 0.5)
      | color (c - (dot normal (normalize eye-target) - 0.72
              | clamp -1 0
              | step 0))
      | color [c.b c.g c.r]))
(Excerpt from the logo of https://toodle.studio -- https://gist.github.com/ianthehenry/612c980f0db04ea3c2ccab27...)

Is this even Janet anymore? I dunno. It's a Janet dialect, and it's implemented as regular old Janet macros. But it's much easier for me to type like this. I recognize that it makes my code necessarily single-player, but that's fine for the sorts of dumb projects that I do for fun.

I think a lot of lisp programmers use paredit exactly so that they can write (f (g x)) in the order g x f, but with their editor re-positioning their cursor and taking care of paren-wrapping automatically. But I don't use paredit, and I don't want to wed myself to a particular mode of editing anyway. So I like a syntax that lets me type in the order that I think.

[+] lispm|3 years ago|reply
The 'terminal' restriction is long gone. Most Lisp read-eval-print-loops run inside an editor or another specialized tool.

In Common Lisp:

  CL-USER 37 > (sin 3)
  0.14112

  CL-USER 38 > (cos *)
  0.9900591
Above really is (cos (sin 3)). The variable * is bound to the last result.
[+] bo-tato|3 years ago|reply
you're totally right that it's often easier to read in the order the functions are being applied. That's why just about every lisp/scheme family language has thread-first and thread-last macros -> and ->> so f(g(h(x))) could be written as: (-> x h g f) Janet, clojure and racket and probably others have them built in, emacs lisp has it in dash.el, common lisp has it in cl-arrows and other libraries

It's really just about readability preference though not ease of editing, lisp like languages will have paredit/sexp editing shortcuts in your editor, so when you're on (f x), you press one key and it's turned into (<cursor here> (f x))

[+] tincholio|3 years ago|reply
I'm not familiar with Janet, but I know it's Clojure-inspired, so it probably has threading macros, which are like shell pipes on steroids. In practice, when doing REPL-based development, it's very common to use these as opposed to (g(f x)).
[+] tsujp|3 years ago|reply
[+] dang|3 years ago|reply
Thanks! Macroexpanded:

Show HN: Make 3D art in your browser using Lisp and math - https://news.ycombinator.com/item?id=32738654 - Sept 2022 (38 comments)

Janet – a Lisp-like functional, imperative programming language - https://news.ycombinator.com/item?id=28850861 - Oct 2021 (135 comments)

Janet Programming Language - https://news.ycombinator.com/item?id=28255116 - Aug 2021 (114 comments)

Janet: a lightweight, expressive and modern Lisp - https://news.ycombinator.com/item?id=23164614 - May 2020 (269 comments)

Janet – A dynamic language and bytecode VM - https://news.ycombinator.com/item?id=19179963 - Feb 2019 (50 comments)

Janet, a Clojure inspired language for scripting, or embedding in other programs - https://news.ycombinator.com/item?id=19172510 - Feb 2019 (1 comment)

Janet, a bytecode Lisp vm - https://news.ycombinator.com/item?id=18759277 - Dec 2018 (1 comment)

[+] iainctduncan|3 years ago|reply
If you like things like Janet, you might also like s7 Scheme. It is also a minimal Scheme built entirely in C and dead easy to embed. I used it to make Scheme for Max and Scheme for Pd, extensions to the Max and Pd computer music platform to allow scripting them in Scheme. (https://github.com/iainctduncan/scheme-for-max) Janet was one of the options I looked pretty closely at before choosing s7.

The author (Bill Schottstaedt, Stanford CCRMA) is not too interested in making pretty web pages, ha, but the language is great! https://ccrma.stanford.edu/software/snd/snd/s7.html

[+] BWStearns|3 years ago|reply
So this is kind of like a lightweight non-jvm clojure? I like it. Looks like it could be a nice swiss army knife for data munging.
[+] jimbo9991|3 years ago|reply
This is really cool, surprised it has so few stars and I've never seen it talked about here. I like the LISPs but I've always thought not having a CPython type thing was a bit of a deal breaker for making it a go-to "primary" language that I would use everyday.

I'm definitely going to try with Janet though.

[+] masklinn|3 years ago|reply
> I've always thought not having a CPython type thing

What cpython type thing? Do you mean a rich(er) set of built-in datatypes?

Because lots of lisps have that, some (e.g. clojure) also have reader macros for pseudo-literals.

Even Scheme has had a built-in hashmap since R6RS (2007), and R5RS implementations usually provided hashmaps even if they were not spec-defined. Common Lisp has had a hashmap more or less all along (at least since before ANSI standardisation)

[+] iLoveOncall|3 years ago|reply
> surprised it has so few stars and I've never seen it talked about here

Why? It's a niche language in a dated syntax, with use cases already covered by other languages.

You will downvote because you don't like that, but it doesn't make it less true.

[+] jgrodziski|3 years ago|reply
There is also the jank language [0] that plays in similar fields.

> jank is a general-purpose programming language which embraces the interactive, value-oriented nature of Clojure as well as the desire for native compilation and minimal runtimes. jank is strongly compatible with Clojure.

[0]: https://jank-lang.org/

[+] davbryn|3 years ago|reply
I think the language is unreadable. For me, the purpose of a programming language is to let a human talk to a machine. Show me (for instance) control flow for the example function on the home page. It just isn't making things easier
[+] bmacho|3 years ago|reply
I tried to translate it to python, I translated

    (defn sum3
      "Solve the 3SUM problem in O(n^2) time."
      [s]

      (def tab @{})
      (def solutions @{})
      (def len (length s))

      (for k 0 len
        (put tab (s k) k))

      (for i 0 len
        (for j 0 len

          (def k (get tab (- 0 (s i) (s j))))

          (when (and k (not= k i) (not= k j) (not= i j))
            (put solutions {i true j true k true} true))))

       solutions)
into

    def sum3(s) :
        """Solve the 3SUM problem in O(n^2) time."""
    
        tab = {}
        solutions = {}
        l = len(s)
    
        for k in range(0,l) :
            tab[ s[k] ] = k
            
        for i in range(0,l) :
            for j in range(0,l) :
                
                k = tab.get( -s[i]-s[j] ) 
                
                if k and k != i and k != j and i != j :
                    solutions[ {i:True, j:True, k:True} ] = True
                    
        return solutions 
pretty much the same. Python is not working because it can't hash dicts, while janet interprets {1:True, 2:True} the same as {2:True,1:True} when these are keys (I think?).

In the example janet returns

    (map keys (keys solutions))
instead of the "solution" dict that converts dicts like {{1:True, 2:True} : True} into [[1,2]], but I don't get it how.

But syntactically janet is not much worse(?).

[+] hnlmorg|3 years ago|reply
Readability can be a subjective property.

For anyone who’s spent even a casual amount of time with S-Expressions, the example code is extremely readable. But if ALGOL-like code is your main source of experience, then Janet will look like executable line noise.

[+] StreamBright|3 years ago|reply
I guess: f x -- obscure and mathematical

(f x) -- too many parentheses

f(x) -- PERFECT

[+] modernpink|3 years ago|reply
I assume you've not done LISP or LISP-like languages before?
[+] xigoi|3 years ago|reply
How does the example not show control flow?
[+] simon_acca|3 years ago|reply
Interesting! Janet clearly stands on its own with many unique features, that said if you are looking for a "scripting Clojure", also consider Babashka which can tap into many very high quality libraries from the Clojure ecosystem.

https://github.com/babashka/babashka

[+] gleenn|3 years ago|reply
Syntax looks a whole lot like Clojure. I immediately appreciate the optional immutability built in too.
[+] exitb|3 years ago|reply
Make a note though that the immutable counterparts are not persistent, as the Clojure ones are.
[+] moffkalast|3 years ago|reply
Is it a good place Janet or a bad place Janet though?
[+] ElfinTrousers|3 years ago|reply
I decline to use this language unless it's Disco Janet.
[+] epgui|3 years ago|reply
This is Lisp, it’s definitely the Good Place (TM).
[+] tmtvl|3 years ago|reply
It's a shame the new Lisps tend to not rip off Common Lisp's type system, being able to declare my types and having SBCL admonish me when I'm doing something stupid is something I would hate to lose.
[+] BaculumMeumEst|3 years ago|reply
Isn’t it more accurate to call that unspecified SBCL behavior than “common lisp’s type system”?
[+] 2h|3 years ago|reply
this is really cool. I love how small it is. with the Windows package for example, you actually only need "janet.exe", which is only 791 KB.
[+] WalterBright|3 years ago|reply
I just can't let go of infix notation.
[+] kensai|3 years ago|reply
Sounds a lot like the space of Lua. How do they compare?
[+] ianthehenry|3 years ago|reply
They're similar! The author of Janet previously wrote https://fennel-lang.org/, a compile-to-Lua language.

Janet has more traditional scoping rules than Lua. Tables and arrays are separate types in Janet, and arrays are 0-indexed. Biggest runtime difference is probably that Janet has value types.

I think the compile-time programming is the real differentiator, but it's hard to summarize what that means in a comment.

Performance is pretty similar to the vanilla Lua interpreter in the benchmarks I've seen and run (Janet typically wins slightly), but there's no LuaJIT.

[+] gigatexal|3 years ago|reply
The lisp style syntax is jarring
[+] wheelerof4te|3 years ago|reply
Honestly, to me it is unreadable. Defining function inside a function inside yet another function where you use a for loop kind of function.

It requires a much different line of thinking to be applicable in the real world, which goes against historic human nature of following specific instructions, one instruction at a time.