Hacker News new | past | comments | ask | show | jobs | submit login
Why Janet? (ianthehenry.com)
405 points by ianthehenry on April 13, 2023 | hide | past | favorite | 135 comments



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.


Looking up the function at runtime all the time, was at the time of the design of these Lisp systems exactly the thing which one wanted to avoid. Macros were designed such that a compiled application does no additional macro expansions at runtime or need to resolve functions. A compiled application could also be one which did not include a development environment and/or was compiled to static code. Computers were slow and the applications were ambitious, the competition was using C/C++. Such a graphics example would have been written in the context of a CAD system or a graphics design software. There was little need to lookup the mostly same functions all the time, but more need to have very fast code.


Janet's approach seems a little strange to me in that it's opt-in rather than opt-out. In CL or Clojure, you just normally can't have this issue due to the package or namespace system (e.g. you have to go out of your way to define an anaphoric macro that will work anywhere). Clojure doesn't have a separate function namespace either, but since backquote will automatically qualify symbols with their ns unless you opt out, you don't need to use any strange (at least to me) unquoting approach.

The "separate package" issue is really a non-issue, especially in Clojure where you have a different namespace for every file. Some people prefer a separate package for every file in CL, but even with one-package-per-project, you only have to worry about your own code. Anyone redefining functions outside your package isn't going to cause any problems with your macros.


Basically fexprs


Funny you mention that, fexprs are definitely better behaved when they bind the function itself instead of a name by which the function might be found later.


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:

    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.

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


If you read into the Yggdrasil project page, it basically says ports of other platforms call others over the network:

> By reaching out into the internals of each port using a remote web connection from the centre, we can systematically harness the work done and maintain the system using only 14 connections and not 196

Source: https://shenlanguage.org/yggdrasil.html

The impression I got was if you don't want to use the SBCL backend (and maybe the JS backend), the ecosystem is not super mature. I don't do any dev with(in) Windows outside of WSL, so that's not something I care about. I find Shen interesting as an academic system, but I'm not sure I would consider it for personal tooling or production code. Gerbil doesn't seem like an academic project, it aims to be a batteries included Scheme it seems.


Yes, that's the Yggdrasil project; you can use any port of Shen to create code in Shen bringing type checking to Python or JavaScript for example.

Does Gerbil really have a lot of real-world projects vs. Shen?

I am trying to use Shen for safety related control systems. It is more formal and verifiable than Gerbil. I started in SPARK2014 and I am just playing with Shen to see if it goes better.

The videos I linked to in another post in this thread show how you can use Shen to program front end stuff with the JavaScript port of Shen.


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.


Why not? There is an external IDE (SLIME in GNU Emacs) which interacts with a language server to get information about syntax highlighting, completion, documentation, source locations, ...

The idea of an 'inferior Lisp' connected to a GNU Emacs has a long tradition. Usually one did communicate with a Lisp as a process on the same machine. That was long before SLIME. Popular were ILISP (1990) or ELI (for Allegro CL).

The purpose is a more general, providing evaluation/compilation, backtraces, inspectors, browsers, etc.


Guiser fills the role of a language server for Guile and a handful of other Schemes.


Yes, there are a handful of Geiser implementations, but they vary differ in how feature-complete they are. Guile's implementation is the best supported and it's pretty great, but with Chez or MIT you'll have a trickier time.


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


Or OCaml


Or ML


Maybe


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


> 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

These things help you learn to program, but they do not help you program.

At some point the box is open, at some point your problems are big and nobody else has done them before. It is in this moment that CL is good, and perhaps better than anything else.

> There's just... a lot of content.

That's some of the stuff that helps you program, because if you didn't have it and you needed it, you'd have to write it.

> no singular approach to learning it works for everyone.

So what? "Everyone" is your competition. Why do you care what works for them?


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?


Well, tastes in programming languages differ from person to person of course; you may as well ask what makes Ada better or worse than Pascal or Algol68 and Rust.

I personally am very fond of the strong support SBCL has for type checking, which is something I find many Lisp-inspired languages just don't seem to care about.

Every programming language has its strengths and weaknesses, S-expression based languages are no exception, though what I consider a weakness in a language someone else may consider a strength. Things that come to mind immediately are CL's case insensitivity (Ricardo Signes' talk A Million Billion Squiggly Characters touches on the problems with case insensitiveness), Clojure's decision to randomly use square brackets for expressions that have no need for them (this is one thing I know that other people are fans of), and Bel's streams operating on bits (as in 1/8th of a byte).

Though what got me to try CL in the first place was someone mentioning that SBCL is fast, and given that it is still a GCed language, it really is. With Scheme I was bouncing back and forth between Scheme and C because I was unsatisfied with the performance, but with CL I don't feel a need to use C as it is fast enough.


Many Common Lisp users unfortunately have this tendency to refer to other Lisp dialects as "not real Lisp" and often don't bother to explore the value propositions of the newer dialects, particularly Clojure-inspired dialects such as Janet. At least, this has been my experience as a Clojure user for several years now.


I would think that there are many dialects, but they share a common core. Common Lisp is just one. Others would be Emacs Lisp, Visual Lisp, Interlisp, Standard Lisp, ISLisp (which is an ISO standardized Lisp). Something like Janet is further away, given that it is not a 'List Processor' - it does not use linked lists as a core data structure. It would be more a 'Maps Processor'. Rounded parentheses are neither sufficient nor necessary to be a 'real Lisp'. In a wider definition JavaScript is a Lisp. Maybe, but IMHO it is not very helpful to and it's not the most important categorisation for it. Personally I would also think that a language is not less useful/interesting when not labelled as core Lisp. A label does not make a language 'better' or 'worse', it just makes clear what to expect.

> often don't bother to explore the value propositions of the newer dialects

There are a lot of languages I would find interesting, besides those that one might or might not label as 'Lisp': Prolog, Erlang, Julia, Rust, OCAML, ...


There is endless variety in lisps and lisp-like languages. It's one of this language family's greatest strengths, and greatest weaknesses.


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.


I can't speak for parent but I'm currently writing a one-man SaaS using common lisp on the backend and vanilla js on the frontend, and it's been great so far. SBCL's compiler produces very performant code and I find I can be very productive in lisp. The library ecosystem isn't quite as rich as, say, python's but it's still good.

My chief complaint is that if you stray too far off the "happy path" of well-known libraries the quality (or even presence of) the documentation tends to decrease dramatically.


Oh, cool. I’m thinking about something similar with SBCL plus vanilla JS. I’m not worried about libraries and these days try to be a library/framework free as possible. The web dev basics of an application server, access to an RDBMS, and possibly help spitting out HTML/CSS/JS are all I need. And it seems like Common Lisp checks those boxes. I’ll give quicklisp props too as everything I’ve tried to install ran on one go. That’s unfortunately not always my experience elsewhere.

Out of curiosity, have you done Lisp before? I’m curious if productivity gains would be lost in me ramping up.


I've dabbled in lisp on and off for a number of years but have never written it professionally.

Back in the early aughts I learned a much more orthodox way of programming with C++ in first year university and fiddled with a bunch of scripting languages making game mods and stuff in my teens and early 20s. C# is probably what I have written the greatest number of LOC in, so I'm very much an ALGOL normie.

Lisp isn't as weird as people like to pretend it is. Once I wrapped my head around the syntax (which takes maybe an hour) the biggest things I wrestled with were

- the type system (variables don't have types unlike C# and friends, they're just named registers where you can shove anything)

- CLOS (in my opinion it's kind of a leaky abstraction, not as good at encapsulating/hiding the underlying complexity as, say, C# or Java, but CLOS definitely has its strengths)

- the language-adjacent concept and nomenclature like Systems. What Lisp calls Packages are pretty close to what most other languages would call namespaces, and what lisp calls Systems are what other languages call Packages, or maybe libraries.

My experience has been that it's a very expressive language and easy to write once you get into the flow of things.


> (variables don't have types unlike C# and friends, they're just named registers where you can shove anything)

This depends on the implementation one uses: SBCL does type checking:

  * (defvar *a-number* 0)
  *A-NUMBER*
  * (declaim (type (integer 0 100) *a-number*))
  (*A-NUMBER*)
  * (setf *a-number* 10)
  10
  * (setf   *a-number* "ten")
  ; in: SETF *A-NUMBER*
  ;     (SETF *A-NUMBER* "ten")
  ; ==>
  ;   (THE (MOD 101) "ten")
  ; 
  ; caught WARNING:
  ;   Constant "ten" conflicts with its asserted type (MOD 101).
  ;   See also:
  ;     The SBCL Manual, Node "Handling of Types"
  ; 
  ; compilation unit finished
  ;   caught 1 WARNING condition
SBCL does that a compile time, too.

> - the language-adjacent concept and nomenclature like Systems. What Lisp calls Packages are pretty close to what most other languages would call namespaces, and what lisp calls Systems are what other languages call Packages, or maybe libraries.

Lisp is old, these names are from end 70s ("package") and ~1981 ("system") - at that time they were used in the MIT Lisp Machine system software.


>SBCL does type checking

It does, and values have types but variables don't. Without the DECLAIM facility you can do the following:

    (defvar *foo* nil)
    (setf *foo* 123)
    (setf *foo* "dank memes")
Whereas in C# you cannot do the following:

    int i = 123;
    i = "dank memes";
Thats all I meant.


No, the differences is that in Common Lisp / SBCL type declarations are optional (note though: there is also type inference in SBCL), type declaration are more verbose, type declarations are sometimes a separate expression and a variable by default has the type T, unless the compiler can infer another type.

  (declaim (type (integer 0 100) *a-number*))
Above does not declare the type of a value, it declares the type of a variable to be an integer in the range of 0 to 100.

In SBCL we can also query for the type of that variable:

  * (defvar *a-number* 0)
  *A-NUMBER*
  * (declaim (type (integer 0 100) *a-number*))
  (*A-NUMBER*)
  * (SB-CLTL2:VARIABLE-INFORMATION '*a-number*)
  :SPECIAL
  NIL
  ((TYPE MOD 101))
The third returned value is the declared type.

Another example: a function with two local variables. One variable is declared to be of type INTEGER and the other of type STRING. We then try to set one variable to the value of the other:

  * (defun foo (a b)
      (declare (type integer a)
               (type string b))
      (setf a b))

  ; in: DEFUN FOO
  ;     (SETF A B)
  ; ==>
  ;   (THE INTEGER B)
  ; 
  ; caught WARNING:
  ;   Derived type of COMMON-LISP-USER::B is
  ;     (VALUES STRING &OPTIONAL),
  ;   conflicting with its asserted type
  ;     INTEGER.
  ;   See also:
  ;     The SBCL Manual, Node "Handling of Types"
  ;
What you see above is that the SBCL compiler a compile time detects a type error and warns. There are no values involved.

See https://en.wikipedia.org/wiki/Gradual_typing


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.


> It produces huge binaries.

My binary with dozens of dependencies (the compiler, the debugger, a web server and all that's required for a web app) is ±30MB, it starts up in 0.40s. The non-compressed binary weighs 120MB and starts in 0.02s.

> Managing and versioning dependencies is very difficult.

You can use Qlot and CLPM these days.

Alexander of 40ants made a demo: https://www.youtube.com/watch?v=jLkqYVTqM38 (with english subs)


Thankfully SBCL is just an implementation and not the language ;)

There are alternative implementations, like ECL, which is capable of transpiling to C and producing small binaries and dylibs. Then there are implementations like CCL, which have low memory footprint (a freshly booted image consumes ~6.8 MB RAM on my Mac). Lastly, there are commercial implementations like LispWorks and Allegro CL which offer tree-shaking, unlike all other implementations.


That's why Common Lisp is a standard and has different implementations.

ECL for example is called "Embeddable Common Lisp". https://ecl.common-lisp.dev/

ABCL is embeddable into Java.

LispWorks and Allegro CL can compile to shared libraries, which are embeddable.

SBCL, OTOH was not developed with the goal to be embeddable.


see ulisp [0] for a lisp with small memory footprint

there are also many other subsets of common lisp you can use for various things that require a small memory footprint

i also want to know what does Janet bring to the table that a Scheme like a Gerbil [1] does not?

[0] https://ulisp.com

[1] https://cons.io


Does Gerbil run on Windows?


given that Gambit does, i would say it is possible


Thanks! I had only looked at the Gerbil docs and couldn’t find anything about Windows.


Ah Fennel i love that language


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.


Enjoyed reading the post! I think the difference between Racket and Janet DSLs is that with Racket, you can avoid using s-exp altogether.

https://beautifulracket.com/stacker/intro.html


This is actually true of Janet as well, although it is not as nicely supported as it is in Racket. You can "bring your own parser," but still leverage the Janet bytecode compiler and runtime. Sort of... an advanced topic, though. The Racket path is much better trodden :)


Wow good to know! I wish there was an easy way to do it in Janet. It’s a much simpler language compared to Racket. And since it started as someone’s hobby project, I think unlike Racket, it isn’t under much pressure from academia. Academic languages tend to be less suitable for production (Haskell, Racket, Julia, etc.)



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/


Sadly that doesn't features such as modules, fibers, PEGs, macros, etc


How about the official Janet documentation? https://janet-lang.org/docs/index.html


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?



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?



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


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?


I started thinking about a new programming language name and ChatGPT returned the following: Jaida Jocly Jovie Jacey Janel Jolyn Jolie Janna Jazzy Jovia




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: