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!
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.
>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.
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?
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.
> 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.
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]))
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.
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))
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)).
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.
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'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)
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.
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
(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.
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.
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.
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.
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.
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.
[+] [-] ianthehenry|3 years ago|reply
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
[+] [-] mike_hock|3 years ago|reply
[+] [-] pmoriarty|3 years ago|reply
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
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
[+] [-] progre|3 years ago|reply
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
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
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
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
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:
(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
In Common Lisp:
Above really is (cos (sin 3)). The variable * is bound to the last result.[+] [-] bo-tato|3 years ago|reply
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
[+] [-] twism|3 years ago|reply
[+] [-] tsujp|3 years ago|reply
- https://news.ycombinator.com/item?id=28850861
- https://news.ycombinator.com/item?id=28255116
- https://news.ycombinator.com/item?id=23164614
[+] [-] dang|3 years ago|reply
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)
[+] [-] cfiggers|3 years ago|reply
[+] [-] iainctduncan|3 years ago|reply
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
[+] [-] jimbo9991|3 years ago|reply
I'm definitely going to try with Janet though.
[+] [-] masklinn|3 years ago|reply
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
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
> 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
[+] [-] bmacho|3 years ago|reply
In the example janet returns
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
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
(f x) -- too many parentheses
f(x) -- PERFECT
[+] [-] modernpink|3 years ago|reply
[+] [-] xigoi|3 years ago|reply
[+] [-] simon_acca|3 years ago|reply
https://github.com/babashka/babashka
[+] [-] gleenn|3 years ago|reply
[+] [-] exitb|3 years ago|reply
[+] [-] moffkalast|3 years ago|reply
[+] [-] ElfinTrousers|3 years ago|reply
[+] [-] epgui|3 years ago|reply
[+] [-] tmtvl|3 years ago|reply
[+] [-] BaculumMeumEst|3 years ago|reply
[+] [-] 2h|3 years ago|reply
[+] [-] hislaziness|3 years ago|reply
[+] [-] WalterBright|3 years ago|reply
[+] [-] unknown|3 years ago|reply
[deleted]
[+] [-] kensai|3 years ago|reply
[+] [-] ianthehenry|3 years ago|reply
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
[+] [-] wheelerof4te|3 years ago|reply
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.