The issue is not so much that macros are not merely syntactic transformations, but rather than s-expressions alone aren’t sufficiently expressive to represent the syntax of any Lisp with a package system. The scheme macro system solves that by having macros return syntax objects where identifiers used in a macro can carry around a reference to the original context where the macro was defined. So referencing a function in a macro definition will look up the function in the context of where the macro is defined, and return a syntax object that refers to the correct function. To my knowledge, most modern macro systems work this way (Scheme, Dylan, etc.)
Most of the need for hygienic macros goes away if you have a compiler (or macro expander) that warns about shadowing.
Code has to go out of its way to do something silly, like reference a variable which it does not define; and by coincidence this has to be defined by a macro that is used in the same scope (so that it simultaneously evades unbound variable and shadowing diagnosis).
Hygienic macros do not solve accidental name capture in code that contains no macros. Manually written code can contain a mistake of reference due to variable shadowing, creating a bug.
Kind of. Passing around extra context in order to resolve the symbol in the right environment does fix the problem, but it's hard to argue with unqoting the symbol as the better fix. Embed the function foo in the macro instead of the symbol foo and information on how to turn that symbol into the function later.
I agree that Janet's approach is nicer, but here's an argument against it: if you unquote a function in a macro and then later redefine the function (like, while doing interactive development in the same running process), the macro expansion will still have the "old" definition. Whereas if you look up the function by (lexical context +) name every time you run the code, you'll pick up re-definitions automatically.
His writing for janet.guide grated me a bit at first, but the guide was good, and then I found myself laughing along. I already liked Janet, but that guide got me back into it. This guide is just confirmation bias for me, but I'll read it because I (now) like the author.
I was considering Shen[1] for this type of use, since it is implemented in many languages. It is a Lisp with built-in Prolog. Feature list from the site's main page:
pattern matching,
lambda calculus consistency,
macros for defining domain specific languages,
optional lazy evaluation,
static type checking based on sequent calculus,
one of the most powerful systems for typing in functional programming,
an integrated fully functional Prolog,
an inbuilt compiler-compiler,
a BSD kernel under 15 languages (Lisp, Python, Javascript, C ...)
and operating systems (Windows, Linux, OS/X),
is extensively documented in a book
has nearly a decade of use.
Aditya Siram made two very good videos on Shen. You can program your front and backend in Shen given Shen is implemented in JavaScript and other PLs.
I know somebody did a port to C, but I am not sure if you can then make an exe as described for Janet.
I looked into Shen recently. I ultimately decided not to use it because their remote compilation model thing seemed... cludgey.
I've since landed on Gerbil Scheme (https://cons.io) which is a modern front-end to Gambit Scheme. It seems like the perfect mix of performance, (modern) features, R*RS, and FFI that I want/need. Baring some significant bugs, it will probably be my goto Lisp going forward.
I am not sure what you mean by "remote compilation".
I know there was Shen Professional that required a monthly subscription and it was on the server, however, Shen is available in different PL ports for free and local use. It is like a Haskelly Lisp. Very well thought out too with a great book that covers a lot of ground including PL history and logic.
I used to use Gambit Scheme, and although I have two linux machines, I work on Windows for my paying jobs. Gerbil doesn't seem to talk about a Windows install. I shy away from WSL, Cygwin, and MSYS2 in general.
I am learning Zig, and I have thought about porting Shen to Zig. The CL port of Shen is the main port (SBCL).
The last time I look at shen it was a complete waste of time, it was like a cooking recipe with all spices combined and then find out the food is still part frozen. Let alone no way to build programs or having 8MB of js programs etc.
I know I should build it myself but I wish Janet, as a Lisp, had better integration with Emacs. There are some modes but they're scattered and not really up to date.
Oh, thats.. certainly not encouraging.. Because other than that, Janet seems interesting. Well.. Maybe one day, we will have good emacs integration, I guess?
I'm super excited about it because I'm fascinated by the author's two other projects, bauble.studio and toodle.studio. It lets me revisit the joy of learning programming and art that I felt when I first go to play with Logo. But in a "serious" and "gorgeous" language and toolset so I can pretend to be an adult in the proper circles.
I specifically love the choice of PEG because I've always hated the "regexp are bad" argument and PEG seems closer to my compilers coursework as well as more advanced.
Could someone compare Janet with Racket? The latter is a much more mature project with a powerful and well designed standard library, so I'm not sure why one would pick Janet.
I see the appeal to embed in another app as a scripting language like Lua, but for writing standalone programs?
Mm, I don't think it makes a lot of sense to compare Janet and Racket. They're so different that they aren't really bidding for the same projects -- you could ask the same question about Janet and Python, or Janet and Ruby, or Janet and Erlang. They aren't really substitutes for one another -- a comparison with Lua or Tcl makes more sense. That said, I think the original article serves as a decent starting point -- how many of the listed points apply to Racket?
That is fair, I agree they don't bid for the same projects.
In terms of listed points.
1. Simplicity... perhaps? I guess Racket has a larger surface area, but just like all Lisps/Schemes it is built on a simple core.
2. Distribution. Janet probably wins here. Racket can produce static binaries, but they may not be as tiny. Relatedly, there is a Racket subset, Zuo, that ticks this box.
3. Parsing text - Racket's whole shtick is this, given it is a language for writing languages :)
4. Subprocess DSL. `sh` looks like a nice library. I can see the value for using this for quick scripts since you can shell out to bash whenever you want.
5. Embeddable. Janet is better here similar to Lua. Also see Zuo.
6. Mutable and immutable collections. Racket has these.
7. Macros. Racket has these.
8. Compile time to run time. I'm not sure about this, being a Racket newbie.
9 and 10 - very subjective :)
In some sense it is a little sad that the Lisp and Scheme like languages diverge so much within themselves, as it makes an already unpopular set of languages even harder to standardize on and evangelize.
Lisps and nil have a symbiotic relationship, not an adversarial one.
They are an everyday falsy value that you program _with_ not around. Nil doesn't signal that you forgot to initialize a value. It simply means stuff like "I don't have this" or "no more". You don't check for it at every corner like an anxious squirrel, but pass it around freely, knowing that your code knows, accommodates and embraces nils.
Option types are inherently a bad idea for languages without value semantics. In rust, the Some variant of an option type can just be allocated on the stack at almost no cost, but doing this here would require a million small allocations every time you want to return something. Beyond that, it makes no sense to have union types when every value is already a union of all possible types.
We used them at work (Clojure) and it didn't solve anything; normal idioms don't apply to monads and the mental overhead of having to know when they are used is the exact same as having to know when something might be nil.
> I like Janet so much that I wrote an entire book about it, and put it on The Internet for free, in the hopes of attracting more Janetors to the language.
Thanks for doing this! Would it be possible to get a copy in EPUB form, so I can read through it on my e-reader?
That's a super hacky epub that I just generated from the markdown source and have not read over thoroughly. Formatting is a bit different, and code blocks have no labels, which are occasionally significant... but from a quick glance it looks pretty close.
Also https://janet.guide/all has the full text of the book, which you could save to HTML and convert to an epub with pandoc (or print to a PDF). Not sure if the results would be better or worse.
I’ve been messing with Janet for a few (maybe 10?) days and I’ve already found a few uses for sh/$ scripts and peg grammar- it hasn’t quite replaced awk for me yet but it’s getting there.
I keep meaning to try Janet, and I think this post is the push I needed to actually do so. IIRC my previous hesitation was around multithreading, but apparently that's supported now so I really don't have much of an excuse left.
I find it interesting to compare Janet to Guile Scheme because they
both claim to be fit as embed-able scripting languages.
Haven't done any practical comparison yet bc Janet has only caught my
eye recently (;p), but it appears to have a culture of vendoring and
an edge in implementation size and complexity which (ideally) enable
diving into the language's implementation to scratch your own itches,
learn how it (and the stack in general) work more throughly, and allow
eg. low level code optimizations with less FFI boilerplate.
I wonder how executable size and performance pan out, but with C FFI
available in each language (and the reality of how much time
optimization is worth), I feel that comforts matter more.
I readily admit, though, that not all software development needs to be flexible and big-application focused. To that end there are many things Common Lisp is not so good at. Common Lisp is not good at being pretty out-of-the-box, it's not good at minimalism, and it's not good at prescriptive or dogmatic programming styles. I've personally not been convinced of the various approaches to using Common Lisp as a scripting language (especially because of standard multi-stage execution). Common Lisp also has an unusual learning curve: there's an initial hump of learning Lisp's oddities (DEFVAR/DEFPARAMETER, CAR/FIRST, no hash table syntax, lots of EQ[UA]L-ity functions, systems vs packages, LOOP/DO/DOTIMES/DOLIST, "Neo, there are no lists in Lisp", ...), followed by a longer path of soaking in the extensive facilities offered by the language. There's just... a lot of content. And in part because of its idiosyncratic nature, no singular approach to learning it works for everyone.
Common Lisp is pretty anemic when it comes to extreme use of functional programming. It's wholly capable and serviceable, but most library writers don't go whole-hog with FP, and Common Lisp by default makes FP a little stuffy (it requires functions bound to variables to be called with FUNCALL, and it requires defined functions to be referenced with #' syntax). There is very little support for functional data structures; the FSET library seems to be what most people suggest.
I’m relatively unfamiliar with lisps. Are all S-expression languages not created relatively equal? What is the difference between a “good” S-expression language and a “poor” one?
Apologies for the question, but to clarify you’re saying that you much prefer the libraries that QuickLisp provides and find Lisp languages lacking.
I’m debating whether to dive into Common Lisp for a new project. I’ve been a fan of Lisp for a while, much prefer parentheses over other notation, and have dabbled over the years by doing things like SICP and writing my own toy Lisp.
Common Lisp comes across as one heck of a battle-tested language and libraries. But I’m debating how much of a learning curve I’ll have on the front end just getting used to SBCL/Emacs/Slime and the various libraries. And I’m not sure that I “get” the interactive workflow yet but def would love to.
Depending on what you're doing, Janet might be a great fit! I wrote a DSL for [expressing and shading 3D shapes](https://bauble.studio), and it was pretty easy. Depending on exactly what you're trying to do, the ease of embedding the Janet interpreter inside of other programs might be a big point in its favor.
Nim's great for writing DSLs as well, plus you can make it statically typed. It ends up being a sort of "yaml-expressions" rather than "s-expressions ". Creating DSLs is quite satisfying in general.
It is so hard to read. Tried reading the book too and couldnt get past chapter 2 because of this „conversational“ style that jumps all over the place with irrelevant nonsense. Ian, hire an editor, please :(
Agreed. What is perfectly fine in a blog post can get tiresome in long form. It is not the question pre-empting structure, which is great in my opinion, but avuncular lines like "Okay wow; we’re just diving right in huh".
Yeah... he says you can learn the language in an afternoon, but his book is so long-winded... I just want a short intro so that I can start writing programs.
I really like the conversational style. If I wanted the quickest, driest way to get started in Janet, I'd probably go to https://learnxinyminutes.com/docs/janet/
The book (which I really like so far, 7 chapters in) is more of a deep dive which covers a lot of the gotchas and intricacies without shying away from the nastier parts of the language. The documentation on the official site should get you going in a couple of hours.
tbh if you find this "condescending", you have to reevaluate your stance towards your general attitude. If you really wish to do so, you can find offensive and condescending wordings literally everywhere, it is always in the eye of the beholder to be offended or not.
I wish the code convention closed brackets in line with the opening brackets rather than at the end of some random line of code. My ADHD/autistic brain does not like the imbalance.
Janet seems really tempting for tiny footprint, distributability etc.
But I'm currently leaning towards to Racket just because it would be more or less compatible with a whole host of Scheme books that I'd like to read (The Little Schemer/Typer/Learner, SICP, Functional Differential Geometry).
Does anyone familiar with Janet know if those books can be easily worked through with Janet for a newbie Lisper?
Racket is a great choice for learning but also very batteries included if you want to make real projects in it.
Janet has a more Clojure-inspired syntax but the semantics and general ideas should carry over. I think trying to work through the books in Janet would be a great extra challenge. You can always drop that and focus on Racket if it becomes it too much for you.
Why not try doing the exercises in Janet? It gives you the end-product goal so you don't have to waste energy on ideation, but having to figure out syntactic differences and compatible standard library functions and macros yourself really helps you understand a language top-to-bottom and is arguably the best way to learn. The way I learn languages all the time is to translate exercises on sites like HackerRank or exercism along with toy projects I already have.
I haven't used Janet but there were some notes in the author's ebook (https://janet.guide/) that suggested it had some small but key differences with a more "traditional" Lisp that might trip you up or at least cause friction. I'd suggest using Racket for those books.
With a little bit of effort you can make it work. But I believe Racket has specific language definitions for some of these books so you can follow them more or less seamlessly.
I'm writing go in my dayjob right now, and my reaction was "hey under a megabyte for a runtime with GC is pretty good!"
So I checked just now, and my hello world for go was 1.9M. And you're well within your rights to just declare "the fact that go is even worse doesn't make Janet good" but let's be honest - plenty of people have accepted go's compromises, so something coming in at better than half the size is relatively much better.
edit: out of curiosity i used gcc to make hello worlds in C and C++, which came in at 32k and 37k respectively.
But those executables have run-times in shared libraries.
It sounds as if Janet's run-time is statically linked; it could also be built as a .so?
Shared library calls (and variable references) are ugly, though. Unless you have a large number of Janet programs, the space saving isn't worth the shared library tax.
There is a cost to having so much language ("all the language, all the time") available at different stages of the program lifecycle (macroexpand, compile, runtime, ...). Not sure if a Janet program can run eval, but it still has a garbage collector, etc.
And that is 379 LOC for an Asteroids clone which includes some inline defined math functions instead of an include to an external math lib. Raylib is great for the GuI stuff (it is an immediate mode GUI, because games), and it is written in C.
in case your comments get flagged and removed, i just want you to know someone appreciated your humor. Technically speaking though, humor is not allowed on hacker news, it's litterally in the TOS....and I only know that from many flags of my own.
We are not amused! Not but that part of the TOS, which I wasn't aware of. I don't see where it says so, though, except maybe the part of the guidelines that say to "Avoid generic tangents".
I do see how it helps keep the contents focused and useful, though. So... Ok, I guess? I'll just have to express my sense of humour as part of something useful, not as the only thing. Thanks for pointing this out.
Sure you need to learn some hard stuff like async, but it seems to me that the most important thing is languages should work “like you expect them to”.
Python, javascript, they’re not perfect but they’re pretty obvious.
Zig aims to be obvious.
Rust is the opposite of obvious. C++, the small subset of it that I use is pretty obvious. Things are similar to other languages, there is minimal surprises.
They only seem obvious to you because you are used to them.
Quite the opposite to you, Rust is obvious to me because I'm used to low-level programming and careful memory management, where JS is more than often very surprising.
Another example: I wrote a Lisp-like language in my company for mathematicians to develop constraint systems; now this purely functional, bastard Lisp feels like a second nature to them, whereas they could barely put three lines of Java together to save their lives.
Does not mean that I'm more or less right than you are; just that just like human languages, no PL or paradigms are ‶naturally obvious″, we are just more or less used to them.
How does the quote go again, someone can learn Lisp in a week, except if they know C then it will take three weeks? I'm sure I've got the quote completely wrong (maybe it was about Forth?), but the essence is there: if you're learning a programming language it may take longer if you're already used to a programming language with different semantics compared to getting into it from scratch.
Also Raku is the most obvious language because it inherited the "it" variable ($_) from Perl, and "it" is unbeatable for writing obvious code (read a line, uppercase it, replace all numbers in it with dollar signs, and print it out again).
That's very subjective. Of the languages you mention JavaScript, C++ and Zig are not obvious to me at all – I usually find myself on Stackoverflow looking for answers when trying to write even the simplest things in them.
Janet on the other hand felt instantly familiar after reading the language introduction. It has all the usual imperative constructs from C-family languages – for, while, if – but also extremely expressive macros like `loop` and `as->` that are more common in the world of lisps, and `partial`/`comp` from the functional paradigm. For such a small language Janet really covers a lot of bases.
Functions/macros are often called exactly what I expected them to be, which has allowed me to figure out how to do most things by simply guessing in the REPL. If I need to figure out the details I can use the `doc` function (or Ctrl-g in REPL), and the docs work perfectly even for what would be keywords in non-lisp languages. It has been a really pleasant experience that I haven't really had with other languages, aside from maybe Julia which also has very intuitive syntax and function naming conventions.
I used to work at a US EMR company, not as a programmer though. They use a terrible language called M or MUMPS. New hires would spend their first month or so literally in a classroom with an instructor and homework to learn the language (and other company specific things).
Now I'm not recommending you use an unusual language, but I think most companies don't spend enough time on boarding new hires and expecting professional development happen on the employees time.
If I am going to use a tool every day for a couple years I want the best tool, not the easiest tool to learn how to use.
Functional programming it's quite obvious, actually: there's data, there's transformations, there's data again. Sometimes there's side effects. Doesn't math work that way? I'm no expert on it, but the key for me was understanding that usually, in functional languages, you actually write _less_ functions, and most of the application behavior is expressed through static data.
I remember the first time I encountered a C-like for loop, and felt as a most alien thing: until this day I still feel it's an unnatural construct, a third of it abstracted from the application logic, the other two halves low-level requisites. And of course, watching that classic video of Rich Hickey destroying Java's HTTPServletRequest class just once made it clear, at least for me, that OOP's stance to redefine every data input and output into its own understanding of the universe isn't the obvious approach at all.
> Python, javascript, they’re not perfect but they’re pretty obvious.
It’s really strange to call JavaScript obvious… strange implicit conversions, broken equality operator, strange variable scoping… I still have no clear idea what the `this` keyword does in the majority of circumstances…
I was mostly used to C and Perl when I was young, and Python made a lot of sense.
I would not say that javascript was more obvious to me than rust was when I started to use them.
A lot of people seem to be hung up on borrow checker, but if you are used to low level C, you had to keep track of that with comments on functions and conventions anyway (and you had to keep it in your head).
The thing that I had to get used the most of in rust is using more functional style of programming, but since I liked python comprehensions and collections, It want such a long jump as it otherwise might be.
There's probably a distinction to be made between being obvious and having no surprises. I agree Python is relatively obvious, and I write a lot of small python scripts for data analysis because if I can figure out what I want to do, doing it in python is usually very simple. But for larger programs I care less about obvious, and more about no unpleasant surprises. Once the amount of code grows larger than I can hold in my head at once, I really want the language to help me avoid footguns. Python mostly isn't great for this due to its type system (with a few exceptions such as its big integer support avoiding most integer overflow surprises).
It probably isn't a popular opinion, but I actually think C++ does a fairly good job of this, so long as you're disciplined in how you use it. That discipline is not necessarily obvious, but I've acquired it over 30+ years, and now I find that I almost never have memory safety issues (I do use sanitisers here, but they rarely show up anything). Perhaps more importantly, C++'s fairly strong typing means I'm generally pretty confident refactoring code as requirements change without getting unpleasant surprises. Sure, C++ could do better in many ways, but it is pretty good at avoiding many of the unpleasant surprises I care about, at least for single-threaded code.
Pattern matching and referential transparency are much more "obvious" or "natural" (meaning closer to the way we humans think) than javascript and its idiosyncrasies (think that even something like `x = x+1` in imperative languages is an infamous tripping point for beginners). C++ is definitely anything but obvious.
obvious is too subjective here, what is not obvious in FP ? is it the terminology ? or the accumulated culture (monads and such) ? because FP at its core is pretty much very obvious. Functions from domain to domain, that's it.
I somewhat agree with this, note that Janet is not very functional, when I briefly used it I never used a single recursive function. It's meant to be used more in an imperative way.
I started thinking about a new programming language name and ChatGPT returned the following:
Jaida
Jocly
Jovie
Jacey
Janel
Jolyn
Jolie
Janna
Jazzy
Jovia
The discursion on macros is interesting reading: https://ianthehenry.com/posts/janet-game/the-problem-with-ma.... But it stops at Scheme’s hygienic macros, which address the very problem raised at the beginning of the discussion, in more or less the way Janet seems to work: https://legacy.cs.indiana.edu/ftp/techreports/TR356.pdf (page 4).
The issue is not so much that macros are not merely syntactic transformations, but rather than s-expressions alone aren’t sufficiently expressive to represent the syntax of any Lisp with a package system. The scheme macro system solves that by having macros return syntax objects where identifiers used in a macro can carry around a reference to the original context where the macro was defined. So referencing a function in a macro definition will look up the function in the context of where the macro is defined, and return a syntax object that refers to the correct function. To my knowledge, most modern macro systems work this way (Scheme, Dylan, etc.)
Most of the need for hygienic macros goes away if you have a compiler (or macro expander) that warns about shadowing.
Code has to go out of its way to do something silly, like reference a variable which it does not define; and by coincidence this has to be defined by a macro that is used in the same scope (so that it simultaneously evades unbound variable and shadowing diagnosis).
Hygienic macros do not solve accidental name capture in code that contains no macros. Manually written code can contain a mistake of reference due to variable shadowing, creating a bug.
Kind of. Passing around extra context in order to resolve the symbol in the right environment does fix the problem, but it's hard to argue with unqoting the symbol as the better fix. Embed the function foo in the macro instead of the symbol foo and information on how to turn that symbol into the function later.
I agree that Janet's approach is nicer, but here's an argument against it: if you unquote a function in a macro and then later redefine the function (like, while doing interactive development in the same running process), the macro expansion will still have the "old" definition. Whereas if you look up the function by (lexical context +) name every time you run the code, you'll pick up re-definitions automatically.
4 replies →
This is second article I read about Janet and by the same author (of course) and I’m really intrigued (especially since I am proficient in ELisp).
Yet beyond that, I’m really taken by author’s passion for the language. That’s one great language advocacy at work.
I can really feel the author’s excitement in every sentence. Reading his work is great fun, and really does make me want to use Janet
I agree. I prefer Shen, but Janet has been intriguing me for the past few years. Darn, I'm going to spend the weekend (nights) playing with Janet!
His writing for janet.guide grated me a bit at first, but the guide was good, and then I found myself laughing along. I already liked Janet, but that guide got me back into it. This guide is just confirmation bias for me, but I'll read it because I (now) like the author.
I was considering Shen[1] for this type of use, since it is implemented in many languages. It is a Lisp with built-in Prolog. Feature list from the site's main page:
Aditya Siram made two very good videos on Shen. You can program your front and backend in Shen given Shen is implemented in JavaScript and other PLs.
I know somebody did a port to C, but I am not sure if you can then make an exe as described for Janet.
https://www.youtube.com/watch?v=lMcRBdSdO_U
https://www.youtube.com/watch?v=BUJNyHAeAc8
You implement Kλ in your PL of choice if it is not already available, and voila, you can program in Shen!
[1] https://shenlanguage.org/
I looked into Shen recently. I ultimately decided not to use it because their remote compilation model thing seemed... cludgey.
I've since landed on Gerbil Scheme (https://cons.io) which is a modern front-end to Gambit Scheme. It seems like the perfect mix of performance, (modern) features, R*RS, and FFI that I want/need. Baring some significant bugs, it will probably be my goto Lisp going forward.
I am not sure what you mean by "remote compilation".
I know there was Shen Professional that required a monthly subscription and it was on the server, however, Shen is available in different PL ports for free and local use. It is like a Haskelly Lisp. Very well thought out too with a great book that covers a lot of ground including PL history and logic.
I used to use Gambit Scheme, and although I have two linux machines, I work on Windows for my paying jobs. Gerbil doesn't seem to talk about a Windows install. I shy away from WSL, Cygwin, and MSYS2 in general.
I am learning Zig, and I have thought about porting Shen to Zig. The CL port of Shen is the main port (SBCL).
2 replies →
The last time I look at shen it was a complete waste of time, it was like a cooking recipe with all spices combined and then find out the food is still part frozen. Let alone no way to build programs or having 8MB of js programs etc.
I know I should build it myself but I wish Janet, as a Lisp, had better integration with Emacs. There are some modes but they're scattered and not really up to date.
Does Janet have a language server yet? That would go a long way, but last time I checked it did not
Do any lisps have a "language server"? They kinda are their own language server right?
If you mean something like SLIME/SLY, than yeah, but I guess I never considered that a language server in the same way as all the other LSP backends.
3 replies →
Oh, thats.. certainly not encouraging.. Because other than that, Janet seems interesting. Well.. Maybe one day, we will have good emacs integration, I guess?
Fwiw, I love the Janet for Mortals book.
I'm super excited about it because I'm fascinated by the author's two other projects, bauble.studio and toodle.studio. It lets me revisit the joy of learning programming and art that I felt when I first go to play with Logo. But in a "serious" and "gorgeous" language and toolset so I can pretend to be an adult in the proper circles.
I specifically love the choice of PEG because I've always hated the "regexp are bad" argument and PEG seems closer to my compilers coursework as well as more advanced.
So much to love about Janet.
Ian’s previous post on the subject for anyone curious: https://news.ycombinator.com/item?id=35386405 (12 days ago, 157 comments)
Could someone compare Janet with Racket? The latter is a much more mature project with a powerful and well designed standard library, so I'm not sure why one would pick Janet.
I see the appeal to embed in another app as a scripting language like Lua, but for writing standalone programs?
Mm, I don't think it makes a lot of sense to compare Janet and Racket. They're so different that they aren't really bidding for the same projects -- you could ask the same question about Janet and Python, or Janet and Ruby, or Janet and Erlang. They aren't really substitutes for one another -- a comparison with Lua or Tcl makes more sense. That said, I think the original article serves as a decent starting point -- how many of the listed points apply to Racket?
That is fair, I agree they don't bid for the same projects.
In terms of listed points.
1. Simplicity... perhaps? I guess Racket has a larger surface area, but just like all Lisps/Schemes it is built on a simple core. 2. Distribution. Janet probably wins here. Racket can produce static binaries, but they may not be as tiny. Relatedly, there is a Racket subset, Zuo, that ticks this box. 3. Parsing text - Racket's whole shtick is this, given it is a language for writing languages :) 4. Subprocess DSL. `sh` looks like a nice library. I can see the value for using this for quick scripts since you can shell out to bash whenever you want. 5. Embeddable. Janet is better here similar to Lua. Also see Zuo. 6. Mutable and immutable collections. Racket has these. 7. Macros. Racket has these. 8. Compile time to run time. I'm not sure about this, being a Racket newbie. 9 and 10 - very subjective :)
Cheers! Thanks for the great article!
https://docs.racket-lang.org/zuo/index.html
In some sense it is a little sad that the Lisp and Scheme like languages diverge so much within themselves, as it makes an already unpopular set of languages even harder to standardize on and evangelize.
I wish Janet used Option types instead of using nil. No idea why modern Lisps don't borrow from Rust's goodness.
Lisps and nil have a symbiotic relationship, not an adversarial one.
They are an everyday falsy value that you program _with_ not around. Nil doesn't signal that you forgot to initialize a value. It simply means stuff like "I don't have this" or "no more". You don't check for it at every corner like an anxious squirrel, but pass it around freely, knowing that your code knows, accommodates and embraces nils.
Option types are inherently a bad idea for languages without value semantics. In rust, the Some variant of an option type can just be allocated on the stack at almost no cost, but doing this here would require a million small allocations every time you want to return something. Beyond that, it makes no sense to have union types when every value is already a union of all possible types.
I love option types, but I don't think they make a lot of sense in dynamically typed languages.
Agreed.
We used them at work (Clojure) and it didn't solve anything; normal idioms don't apply to monads and the mental overhead of having to know when they are used is the exact same as having to know when something might be nil.
Option types are from ML, not rust. ML predates rust by about 40+ years.
Rust did not invent Option types.
I would say from Boost C++ or from C++ std. Rust came far later.
Or from Haskell
3 replies →
Oh, that’s funny.
> I like Janet so much that I wrote an entire book about it, and put it on The Internet for free, in the hopes of attracting more Janetors to the language.
Thanks for doing this! Would it be possible to get a copy in EPUB form, so I can read through it on my e-reader?
Hmm, maybe?
https://janet.guide/janet-for-mortals.epub
That's a super hacky epub that I just generated from the markdown source and have not read over thoroughly. Formatting is a bit different, and code blocks have no labels, which are occasionally significant... but from a quick glance it looks pretty close.
Also https://janet.guide/all has the full text of the book, which you could save to HTML and convert to an epub with pandoc (or print to a PDF). Not sure if the results would be better or worse.
Ok, the shell scripting DSL has got me interested.
Same, but I'm not sure if it's enough to pick it up.
I’ve been messing with Janet for a few (maybe 10?) days and I’ve already found a few uses for sh/$ scripts and peg grammar- it hasn’t quite replaced awk for me yet but it’s getting there.
I keep meaning to try Janet, and I think this post is the push I needed to actually do so. IIRC my previous hesitation was around multithreading, but apparently that's supported now so I really don't have much of an excuse left.
I would like to compare this thing with real Lisps in the terms of abilities.
Also I would like to know the correct transcription of the name.
I find it interesting to compare Janet to Guile Scheme because they both claim to be fit as embed-able scripting languages.
Haven't done any practical comparison yet bc Janet has only caught my eye recently (;p), but it appears to have a culture of vendoring and an edge in implementation size and complexity which (ideally) enable diving into the language's implementation to scratch your own itches, learn how it (and the stack in general) work more throughly, and allow eg. low level code optimizations with less FFI boilerplate.
I wonder how executable size and performance pan out, but with C FFI available in each language (and the reality of how much time optimization is worth), I feel that comforts matter more.
Color me intrigued!
I mean, let's be real: if you have SBCL and QuickLisp most newer S-expression based languages don't have much to offer.
no
I readily admit, though, that not all software development needs to be flexible and big-application focused. To that end there are many things Common Lisp is not so good at. Common Lisp is not good at being pretty out-of-the-box, it's not good at minimalism, and it's not good at prescriptive or dogmatic programming styles. I've personally not been convinced of the various approaches to using Common Lisp as a scripting language (especially because of standard multi-stage execution). Common Lisp also has an unusual learning curve: there's an initial hump of learning Lisp's oddities (DEFVAR/DEFPARAMETER, CAR/FIRST, no hash table syntax, lots of EQ[UA]L-ity functions, systems vs packages, LOOP/DO/DOTIMES/DOLIST, "Neo, there are no lists in Lisp", ...), followed by a longer path of soaking in the extensive facilities offered by the language. There's just... a lot of content. And in part because of its idiosyncratic nature, no singular approach to learning it works for everyone.
Common Lisp is pretty anemic when it comes to extreme use of functional programming. It's wholly capable and serviceable, but most library writers don't go whole-hog with FP, and Common Lisp by default makes FP a little stuffy (it requires functions bound to variables to be called with FUNCALL, and it requires defined functions to be referenced with #' syntax). There is very little support for functional data structures; the FSET library seems to be what most people suggest.
https://old.reddit.com/r/lisp/comments/123edgv/im_considerin...
1 reply →
I’m relatively unfamiliar with lisps. Are all S-expression languages not created relatively equal? What is the difference between a “good” S-expression language and a “poor” one?
4 replies →
Apologies for the question, but to clarify you’re saying that you much prefer the libraries that QuickLisp provides and find Lisp languages lacking.
I’m debating whether to dive into Common Lisp for a new project. I’ve been a fan of Lisp for a while, much prefer parentheses over other notation, and have dabbled over the years by doing things like SICP and writing my own toy Lisp.
Common Lisp comes across as one heck of a battle-tested language and libraries. But I’m debating how much of a learning curve I’ll have on the front end just getting used to SBCL/Emacs/Slime and the various libraries. And I’m not sure that I “get” the interactive workflow yet but def would love to.
6 replies →
Strongly disagree. SBCL is not embeddable. It produces huge binaries. Managing and versioning dependencies is very difficult.
Newer dialects like Janet and Fennel address these deficiencies.
8 replies →
I’m on the verge of trying Racket to write my own DSLs. I wonder if Janet is better for this task?
Depending on what you're doing, Janet might be a great fit! I wrote a DSL for [expressing and shading 3D shapes](https://bauble.studio), and it was pretty easy. Depending on exactly what you're trying to do, the ease of embedding the Janet interpreter inside of other programs might be a big point in its favor.
3 replies →
https://www.jetbrains.com/mps/
Nim's great for writing DSLs as well, plus you can make it statically typed. It ends up being a sort of "yaml-expressions" rather than "s-expressions ". Creating DSLs is quite satisfying in general.
Anyone here fans of the good place? https://thegoodplace.fandom.com/wiki/Janet
It is so hard to read. Tried reading the book too and couldnt get past chapter 2 because of this „conversational“ style that jumps all over the place with irrelevant nonsense. Ian, hire an editor, please :(
Agreed. What is perfectly fine in a blog post can get tiresome in long form. It is not the question pre-empting structure, which is great in my opinion, but avuncular lines like "Okay wow; we’re just diving right in huh".
Yeah... he says you can learn the language in an afternoon, but his book is so long-winded... I just want a short intro so that I can start writing programs.
I really like the conversational style. If I wanted the quickest, driest way to get started in Janet, I'd probably go to https://learnxinyminutes.com/docs/janet/
2 replies →
The book (which I really like so far, 7 chapters in) is more of a deep dive which covers a lot of the gotchas and intricacies without shying away from the nastier parts of the language. The documentation on the official site should get you going in a couple of hours.
+1 I find it hard not to read that sort of writing style as condescending.
tbh if you find this "condescending", you have to reevaluate your stance towards your general attitude. If you really wish to do so, you can find offensive and condescending wordings literally everywhere, it is always in the eye of the beholder to be offended or not.
I wish the code convention closed brackets in line with the opening brackets rather than at the end of some random line of code. My ADHD/autistic brain does not like the imbalance.
I'm eager to jump into a Lisp.
Janet seems really tempting for tiny footprint, distributability etc.
But I'm currently leaning towards to Racket just because it would be more or less compatible with a whole host of Scheme books that I'd like to read (The Little Schemer/Typer/Learner, SICP, Functional Differential Geometry).
Does anyone familiar with Janet know if those books can be easily worked through with Janet for a newbie Lisper?
Racket is a great choice for learning but also very batteries included if you want to make real projects in it.
Janet has a more Clojure-inspired syntax but the semantics and general ideas should carry over. I think trying to work through the books in Janet would be a great extra challenge. You can always drop that and focus on Racket if it becomes it too much for you.
Why not try doing the exercises in Janet? It gives you the end-product goal so you don't have to waste energy on ideation, but having to figure out syntactic differences and compatible standard library functions and macros yourself really helps you understand a language top-to-bottom and is arguably the best way to learn. The way I learn languages all the time is to translate exercises on sites like HackerRank or exercism along with toy projects I already have.
I haven't used Janet but there were some notes in the author's ebook (https://janet.guide/) that suggested it had some small but key differences with a more "traditional" Lisp that might trip you up or at least cause friction. I'd suggest using Racket for those books.
With a little bit of effort you can make it work. But I believe Racket has specific language definitions for some of these books so you can follow them more or less seamlessly.
dont overwhelm yourself. use the language the book was designed for
common lisp also has some really great books, beginner and advanced. paip is probably the most famous of these
Are you familiar with Lua? How about Fennel?
because Janet is a lisp-1, this should be very doable.
Is there SLIME/SLY/NREPL etc. for Janet?
Yes! With spork/netrepl and Conjure in Neovim
https://janet-lang.org/api/spork/netrepl.html
https://github.com/Olical/conjure
I thought this was going to be about the British Academia/Government ISP.
https://en.wikipedia.org/wiki/JANET
And me!
Lisp is good
"hellp world" being a 784k exe is bad
I'm writing go in my dayjob right now, and my reaction was "hey under a megabyte for a runtime with GC is pretty good!"
So I checked just now, and my hello world for go was 1.9M. And you're well within your rights to just declare "the fact that go is even worse doesn't make Janet good" but let's be honest - plenty of people have accepted go's compromises, so something coming in at better than half the size is relatively much better.
edit: out of curiosity i used gcc to make hello worlds in C and C++, which came in at 32k and 37k respectively.
But those executables have run-times in shared libraries.
It sounds as if Janet's run-time is statically linked; it could also be built as a .so?
Shared library calls (and variable references) are ugly, though. Unless you have a large number of Janet programs, the space saving isn't worth the shared library tax.
What are you comparing the size to?
I think Lua is in the same range, so this seems reasonable (but obviously much larger than anything without an included runtime).
That doesn't seem that bad to me for a Lisp.
Common Lisp / SBCL equivalent for me is 41MB.
Clojure überjar size for the same is 4.6MB.
There is a cost to having so much language ("all the language, all the time") available at different stages of the program lifecycle (macroexpand, compile, runtime, ...). Not sure if a Janet program can run eval, but it still has a garbage collector, etc.
Even a Go equivalent is 1.9MB.
Can you write GUIs with Janet?
If you count raylib as a GUI, yes!
https://github.com/tantona/janetroids/blob/master/main.janet
And that is 379 LOC for an Asteroids clone which includes some inline defined math functions instead of an include to an external math lib. Raylib is great for the GuI stuff (it is an immediate mode GUI, because games), and it is written in C.
good enough
[dead]
[flagged]
[flagged]
Janet! Planet! Schmanet!
Somebody had to.
in case your comments get flagged and removed, i just want you to know someone appreciated your humor. Technically speaking though, humor is not allowed on hacker news, it's litterally in the TOS....and I only know that from many flags of my own.
We are not amused! Not but that part of the TOS, which I wasn't aware of. I don't see where it says so, though, except maybe the part of the guidelines that say to "Avoid generic tangents".
I do see how it helps keep the contents focused and useful, though. So... Ok, I guess? I'll just have to express my sense of humour as part of something useful, not as the only thing. Thanks for pointing this out.
I think programming languages should be obvious.
Sure you need to learn some hard stuff like async, but it seems to me that the most important thing is languages should work “like you expect them to”.
Python, javascript, they’re not perfect but they’re pretty obvious.
Zig aims to be obvious.
Rust is the opposite of obvious. C++, the small subset of it that I use is pretty obvious. Things are similar to other languages, there is minimal surprises.
Functional programming feels non obvious.
They only seem obvious to you because you are used to them.
Quite the opposite to you, Rust is obvious to me because I'm used to low-level programming and careful memory management, where JS is more than often very surprising.
Another example: I wrote a Lisp-like language in my company for mathematicians to develop constraint systems; now this purely functional, bastard Lisp feels like a second nature to them, whereas they could barely put three lines of Java together to save their lives.
Does not mean that I'm more or less right than you are; just that just like human languages, no PL or paradigms are ‶naturally obvious″, we are just more or less used to them.
How does the quote go again, someone can learn Lisp in a week, except if they know C then it will take three weeks? I'm sure I've got the quote completely wrong (maybe it was about Forth?), but the essence is there: if you're learning a programming language it may take longer if you're already used to a programming language with different semantics compared to getting into it from scratch.
Also Raku is the most obvious language because it inherited the "it" variable ($_) from Perl, and "it" is unbeatable for writing obvious code (read a line, uppercase it, replace all numbers in it with dollar signs, and print it out again).
$_ is generally referred to as "the topic" in documentation and Raku parlance.
syntactically you can learn lisp in ... an hour? the rest is about reading documentation
That's very subjective. Of the languages you mention JavaScript, C++ and Zig are not obvious to me at all – I usually find myself on Stackoverflow looking for answers when trying to write even the simplest things in them.
Janet on the other hand felt instantly familiar after reading the language introduction. It has all the usual imperative constructs from C-family languages – for, while, if – but also extremely expressive macros like `loop` and `as->` that are more common in the world of lisps, and `partial`/`comp` from the functional paradigm. For such a small language Janet really covers a lot of bases.
Functions/macros are often called exactly what I expected them to be, which has allowed me to figure out how to do most things by simply guessing in the REPL. If I need to figure out the details I can use the `doc` function (or Ctrl-g in REPL), and the docs work perfectly even for what would be keywords in non-lisp languages. It has been a really pleasant experience that I haven't really had with other languages, aside from maybe Julia which also has very intuitive syntax and function naming conventions.
I used to work at a US EMR company, not as a programmer though. They use a terrible language called M or MUMPS. New hires would spend their first month or so literally in a classroom with an instructor and homework to learn the language (and other company specific things).
Now I'm not recommending you use an unusual language, but I think most companies don't spend enough time on boarding new hires and expecting professional development happen on the employees time.
If I am going to use a tool every day for a couple years I want the best tool, not the easiest tool to learn how to use.
Functional programming it's quite obvious, actually: there's data, there's transformations, there's data again. Sometimes there's side effects. Doesn't math work that way? I'm no expert on it, but the key for me was understanding that usually, in functional languages, you actually write _less_ functions, and most of the application behavior is expressed through static data.
I remember the first time I encountered a C-like for loop, and felt as a most alien thing: until this day I still feel it's an unnatural construct, a third of it abstracted from the application logic, the other two halves low-level requisites. And of course, watching that classic video of Rich Hickey destroying Java's HTTPServletRequest class just once made it clear, at least for me, that OOP's stance to redefine every data input and output into its own understanding of the universe isn't the obvious approach at all.
> Python, javascript, they’re not perfect but they’re pretty obvious.
It’s really strange to call JavaScript obvious… strange implicit conversions, broken equality operator, strange variable scoping… I still have no clear idea what the `this` keyword does in the majority of circumstances…
That depends on what you are used to.
I was mostly used to C and Perl when I was young, and Python made a lot of sense.
I would not say that javascript was more obvious to me than rust was when I started to use them.
A lot of people seem to be hung up on borrow checker, but if you are used to low level C, you had to keep track of that with comments on functions and conventions anyway (and you had to keep it in your head).
The thing that I had to get used the most of in rust is using more functional style of programming, but since I liked python comprehensions and collections, It want such a long jump as it otherwise might be.
> mostly used to C and Perl when I was young ... thing that I had to get used [to the most was a] more functional style of programming
When you were a young perl programmer, you didn't use it functionally, as a list processor?
While TMTOWTDI, I've noticed Perl tended to have an imperative camp and a list processing camp.
Does someone with no programming experience at all say the same?
Have also seen cases where people with background in Physics, Mathematics found functional programming more obvious. Obvious is relative.
Even for people with programming experience - once I got hang of Haskell, learning and using Scala professionally became more "obvious" for me.
There's probably a distinction to be made between being obvious and having no surprises. I agree Python is relatively obvious, and I write a lot of small python scripts for data analysis because if I can figure out what I want to do, doing it in python is usually very simple. But for larger programs I care less about obvious, and more about no unpleasant surprises. Once the amount of code grows larger than I can hold in my head at once, I really want the language to help me avoid footguns. Python mostly isn't great for this due to its type system (with a few exceptions such as its big integer support avoiding most integer overflow surprises).
It probably isn't a popular opinion, but I actually think C++ does a fairly good job of this, so long as you're disciplined in how you use it. That discipline is not necessarily obvious, but I've acquired it over 30+ years, and now I find that I almost never have memory safety issues (I do use sanitisers here, but they rarely show up anything). Perhaps more importantly, C++'s fairly strong typing means I'm generally pretty confident refactoring code as requirements change without getting unpleasant surprises. Sure, C++ could do better in many ways, but it is pretty good at avoiding many of the unpleasant surprises I care about, at least for single-threaded code.
Pattern matching and referential transparency are much more "obvious" or "natural" (meaning closer to the way we humans think) than javascript and its idiosyncrasies (think that even something like `x = x+1` in imperative languages is an infamous tripping point for beginners). C++ is definitely anything but obvious.
obvious is too subjective here, what is not obvious in FP ? is it the terminology ? or the accumulated culture (monads and such) ? because FP at its core is pretty much very obvious. Functions from domain to domain, that's it.
I somewhat agree with this, note that Janet is not very functional, when I briefly used it I never used a single recursive function. It's meant to be used more in an imperative way.
For you Janet is obvious or non obvious?
[dead]
[dead]
I started thinking about a new programming language name and ChatGPT returned the following: Jaida Jocly Jovie Jacey Janel Jolyn Jolie Janna Jazzy Jovia