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 and am writing my own tutorial (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 too.)
Janet is distributable
I have like 20 websites and another dozen or so services running on Janet (with the Joy webframework which I wrote a tutorial 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 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 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 (with a 5x longer implementation, hell I have a lisp to Go compiler in 46 lines of Janet) needs 40k lines in direct hashmap format:
package main
func init() {
kjvVerses = []BibleVerse{
{"ge", 1, 1, "In the beginning God created the heaven and the earth."},
{"ge", 1, 2, "And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters."},
{"ge", 1, 3, "And
to be about 3x faster (3.6ms lookups) 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)))
...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.
I think I agree with you on this on the original post is badly worded. "Collections" is really the wrong word; the thing I meant was more like "compound value and reference types." "Immutable collections" makes me think of like persistent data structures (which, while extremely useful, are not available by default in Janet). The symmetry of "value types" and "reference types" is the real thing I like.
I did write "immutable composite values" at the end of that but I don't think I would know what I meant if I didn't write it.
Janet is one of those fresh and embeddable language I’d like to try for embedded scripting in game engine or similar project that requires hot-reloading for rapid iteration without swapping DLLs and such. Lua is great too but Janet feels more expressive in some ways.
Janet is super cool as a language and i would love to use it as a scripting language for a Zig project of some sort. Glad to see more folks bringing it up.
This seems nice and cool, but I'm getting used to Clojure scripting via babashka, and this seems similar. Any great things I'm missing out on besides the embeddability?
Things like "destructuring arrays with a rest argument will do potentially expensive copies" makes it seem like it's generally less "functional", which I don't love.
Destructuring like that is not something you'd do often, janet arrays/tuples are not linked lists. Janet is more like Lua with better functional programming support; it's not a smaller/embeddable Clojure.
veqq | 11 hours ago
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 and am writing my own tutorial (albeit slowly). I even use it in production for all new software (within 3 weeks of starting, I had rewritten all personal scripts etc.)
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 too.)I have like 20 websites and another dozen or so services running on Janet (with the Joy webframework which I wrote a tutorial for), on a single free-tier VPS with 512mb of RAM.
...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 and I've even made combinatorish versions of basic functions:
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:
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
defoutside of main. The following turns a .tsv of the bible into a hashmap in the binary, when compiling:So the rest of the program is literally just accessing the hashmap (twice as fast as the Golang version using
embed):The equivalent go program (with a 5x longer implementation, hell I have a lisp to Go compiler in 46 lines of Janet) needs 40k lines in direct hashmap format:
to be about 3x faster (3.6ms lookups) than the naive Janet.
...but actually Ian Henry means Janet e.g. keeps closures synced across images/sessions:
Exit and start a new REPL session:
It saved the closure and all relevant image in the
(curenv)hashmap. :)veqq | 10 hours ago
Showing off cool stuff in Janet:
The author made https://bauble.studio (with a cool article about it: https://ianthehenry.com/posts/bauble/building-bauble/ ) (notice all the WASM).
A cool Lisp music DSL: https://lisp.trane.studio/ with an academic paper: https://dl.acm.org/doi/abs/10.1145/3677996.3678285 and an example end product: https://x.com/greg_ash/status/1824218993118388708
I just made a library with query syntax over various data structures a la sql:
Printing:
It also has datalog and minikanren (with s expr, sharing the same goals etc.) And it vectorizes like APL:
Or you can just use J directly from Janet:
The Joy Web Framework has a cool db query dsl too:
(var account (db/find-by :account :where {:login (auth-result :login)}))Used for a website's auth:ianthehenry | 10 hours ago
I think I agree with you on this on the original post is badly worded. "Collections" is really the wrong word; the thing I meant was more like "compound value and reference types." "Immutable collections" makes me think of like persistent data structures (which, while extremely useful, are not available by default in Janet). The symmetry of "value types" and "reference types" is the real thing I like.
I did write "immutable composite values" at the end of that but I don't think I would know what I meant if I didn't write it.
heavyrain266 | 10 hours ago
Janet is one of those fresh and embeddable language I’d like to try for embedded scripting in game engine or similar project that requires hot-reloading for rapid iteration without swapping DLLs and such. Lua is great too but Janet feels more expressive in some ways.
vivicat | 5 hours ago
Janet is super cool as a language and i would love to use it as a scripting language for a Zig project of some sort. Glad to see more folks bringing it up.
alect | 2 hours ago
There's actually some discussion in the Janet community about a full zig implementation of Janet! https://janet.zulipchat.com/#narrow/channel/399615-general/topic/Updates.20on.20a.20Zig.20port.20of.20Janet/with/590865945
FedericoSchonborn | an hour ago
Okay then.
alect | an hour ago
yaaa its a bummer the conversation devolved into the AI debate. My understanding is that the Zig community is the least likely to support LLM use...
jeeger | 3 hours ago
This seems nice and cool, but I'm getting used to Clojure scripting via babashka, and this seems similar. Any great things I'm missing out on besides the embeddability?
Things like "destructuring arrays with a rest argument will do potentially expensive copies" makes it seem like it's generally less "functional", which I don't love.
alect | an hour ago
One killer feature of Janet IMO is its built in support of PEGs. It was super convenient when building a textbox DSL for my gamedev side projects
Its something I miss when parsing text in any other language!
sarna | 40 minutes ago
Destructuring like that is not something you'd do often, janet arrays/tuples are not linked lists. Janet is more like Lua with better functional programming support; it's not a smaller/embeddable Clojure.