Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
My Janet Story (junglecoder.com)
89 points by yumaikas on Sept 25, 2021 | hide | past | favorite | 44 comments


> (And yes, despite what the Common Lisp community has to say, Janet is a lisp, even if it’s not a Lisp)

I love this and understand both the sentiment expressed and (as a recovering member of the Cult Of Lisp) the reason why it was said.


I've tried to check why it was said by the CL folks, but I'm only seeing that they feel it's not a Lisp because it lacks cons cells, which I find a very weak argument, since cons cells are an implementation detail in my mind.

The syntax and semantics are clearly similar to that of Lisps before it, and so is the feature set enabled by that very same syntax and the very same semantics. In that sense if Janet had to be the dialect of something else, I don't see what else but being a dialect of Lisp it could be.

And I don't think Janet is different enough in syntax and semantics to be given its own new family of language.

Anyways, those debate are kind of for fun and games, cause it doesn't really matter, but I think the description you could give to someone else who's asking: What's it like? And if you say, it's a bit like programming in any other Lisp, well you'd be giving them a pretty good idea of what it was like, unless they'd never programmed in any other Lisp either, then you're going to struggle, because there's not a whole lot like Lisp.


It's all in the name- lisp : LISt Processing.

That said, I am not such a purist as to be bothered by it, and janet has a special place in my heart right along with chicken, racket, clojure and CL.


What's a list though? Why should it refer specifically to the concrete implementation?

Lists in a conceptual sense are an ordered collection of heterogeneous elements with nestings.

In that sense, code in Janet is very much represented as such and processed as such. Which in my mind makes it a LISt Processing language :p


Lisp is does not stand for generic 'List Processing', but for 'List Processor' -> a specific formalism to process lists.

There is a specific minimal core of Lisp, which consists of a certain minimal set of data structures, operations and an evaluation mechanism.

An early version is described here:

https://dspace.mit.edu/bitstream/handle/1721.1/6096/AIM-008....

Another one is described here:

https://www.ee.ryerson.ca/~elf/pub/misc/micromanualLISP.pdf


That is true, but to engage in a bit of semiotics: those cores are hardly the most appealing part of Lisp for most of the uses cases it's found. It certainly was one way to enable the interactivity and homoiconicty and metacircularity, but, as Clojure and Janet demonstrate, there are a lot of other aspects of Lisp that have value. And while one could (and some have, IIRC) built a Lisp on those minimal data structures that doesn't have parens, most people associate Lips with them being in the strange place.

Cons/car/cdr are far less important, IMO, than eval.

Read, on the other hand, is also technically missing from Janet, and I personally don't mind a great deal, but I could see that being argued.


One of the inventions of McCarthy (and team) was that EVAL can be defined in the core language itself (!) and thus serve as a relatively simple model of computation. Thus the particular EVAL is important, not that source code can be executed at runtime by some api call to execute code.

That's part of the core of the language.

Sure: Many other aspects from Lisp can be found also in Lisp-derived languages and other languages. Like all or some of runtime code loading, symbolic expressions, using symbolic expressions to encode source code, saving and starting heap images, garbage collection, managed memory, source level interpreters/debuggers, self-hosted compilers, read-eval-print-loops, macros, fexprs, integrated interactive development environments, ...


That specific formalism is only specific to McCarthy's implementation though, not even CL counts, as it greatly expands and derives beyond it.

Even McCarthy's lisp had iterations of itself.

Personally, I think the second paper actually points to the essential component for me:

> LISP data are symbolic expressions that can be either atoms or lists. Atoms are strings of letters and digits and other characters not otherwise used in LISP. A list consists of a left parenthesis followed by zero or more atoms or lists separated by spaces and ending with a right parenthesis. The LISP programming language is defined by rules whereby certain LISP expressions have other LISP expressions as values.

I would use that to generalize to a family of languages like so:

""" A Lisp language consists of:

1) Some data representation which are symbolic expressions that can be either atoms or lists. Atoms are strings of letters and digits and other characters not otherwise used in the given Lisp language. A list consists of a starting character (commonly left parentheses) followed by zero or more atoms or lists separated by seperator characters (commonly spaces) and ending with a terminating character (commonly right parenthesis).

2) A programming language whose code uses that data representation and is defined by rules whereby certain Lisp expressions have other Lisp expressions as values. """

Personally I think that's the key aspect, the characteristics of what those rules are, and what the chosen characters are is what creates the family of language, they are the parameters you're allowed to change and must change for it to be a dialect and not just an alternate implementation of the same language.


> That specific formalism is only specific to McCarthy's implementation though,

Not even that. It's the programmatic definition of the core of Lisp. McCarthy's real Lisp implementation was much more featureful.

I would also think the Lisp program in the second paper is 'Lisp Program Zero'. ;-) If your language can run it, it is Lisp. The amount of modifications necessary (syntactic, operators, semantics, ...) gives an indication how far another language is away from it.

Here is a version in Common Lisp:

https://gist.github.com/lispm/d752d5761f7078de4041d4e453e70c...


What's funny is that I came up with a half-decent acronym this morning: PAST.

Paren AST languages.

But this article has been sitting in a draft for the better part of a month, lol.


I mean Racket is a Lisp, but the new syntax they are going for with the Rhombus project is based on Shrubbery notation. Which doesn’t have all the parenthesis


If you like Janet, you'd probably like Clojure a lot as well.

If you want to take on web apps, backend services, machine learning and data-science, desktop GUI applications, mobile applications and/or distributed computing use cases, it could be a good choice where Janet lacks the chops, but Clojure offer a very similar feel while enabling those use cases.

For scripting, CLI, and embedding with a C interface, Janet rocks.


I've been trying to get into clojure but getting a big stacktrace of JVM junk whenever I make a mistake is frustrating.


As someone who learned Clojure before I picked up any sort of Java knowledge, the "big stacktraces from hell/JVM" problem seems kind of over-pushed, it's not as bad as people make it out to be.

Yes, the stack traces are long, but if you actually follow them, they'll show you what your problem is, not sure what else you can wish from a stack trace. I'd rather have too long stack traces with all the details than just a error message without anything to go by.

And since you most likely get your stack trace from when evaluating just a snippet of code with some exception, the stack traces tend to not be as big as for other programming projects (like Java, JavaScript, Golang and else) either where you need to run the entire program in order to reproduce some issue.


I guess what bothers me is that if I'm going out of my way to try and deal with Clojure, I don't want the information with all of the JVM magic (tm) underneath, I just want to know why my Clojure is wrong. Come to think of it, I don't think I've ever really used a hosted language of any kind before, for serious work, so I guess I've just never had to be aware of this.

On a laptop with only a single screen of real estate, even as orderly as the trace might be, it's a distinct hassle to scroll and walk it all the way back to a method argument problem somewhere, and equally frustrating to think about because I'm using a functional language to try and avoid thinking about classes and methods and whatnot.

Maybe I'm just expecting Clojure to taste like Scheme, and I'm probably wrong.


Yeah I think that’s your expectations. Clojure doesn’t treat the JVM as an implementation detail, but as a very important part that you’ll want to use and interact with directly. It wouldn’t make any sense for clojure to not show you the JVM details, because for all it knows, those are the parts that you really care about and you just use clojure to glue it all together


Clojure was something I tried once or twice, but couldn't quite get into, at least back when I tried it before.

These days, I might be better able to pick it up, having cut my REPL teeth on Janet.


There's definitely a higher barrier to entry to Clojure.

There's more layers to understand, because it is hosted over existing runtimes, instead of being self-contained like Janet.

For that I would recommend starting out with Babashka https://babashka.org/ it'll take some of that edge off, because it is a self-contained interpreted Clojure. It's a good way to start where you can get straight to writing code and learning the language itself, and not having to learn about how it is all scaffolded and bootstrapped over a JVM, CLR or JS runtime.

Clojure also forces you almost exclusively into the functional programming style. Doing imperative things in it is ackward. So if you tend to rely mostly on Janet's imperative constructs, this might be a bit of a shock.

Don't really have a trick for this one, just got to learn and practice FP to get familiar with it. Also, it depends a bit what you're doing, a lot of coding questions and small exercises tend to be simpler to implement imperatively, because they're either designed to be so, or are very focused on raw performance. FP will show its worth when writing larger programs in my opinion, where long term productivity, extendability, modularity, maintainability, and robustness/correctness become your biggest concerns. So in the small, it can often seem like a more convoluted way to do things, though for certain problems it also can land itself really well.


One thing I find funny/curious: Does clojure not have a destructuring match in it's standard library? Most people that I've seen move from Clojure to Janet seem to avoid using that.

For me, the two types of programs I tend to use to exercise an language early on are small CLI programs that help me free up mental space, and web apps. Clojure, outside of babashka seems ill-suited to the first (and I've not figured out how to get the CLR clojure up and running).

And when I last tried to do web apps with Clojure, it seemed like everything relied on a lot of self-assmebly, and didn't map cleanly to ways I knew how to web apps at the time from Go, C#,or Erlang. I dunno what the situation is there these days.

For Janet, I did lean into the more imperative constructs early on.


> Does clojure not have a destructuring match in it's standard library?

Yes it does:

    (def my-vector [:mary :had :a :little :lamb])
    (let [[_ _ _ _ lamb] my-vector]
      (print lamb))

    (def person {:name "Bob Dylan" :age 77})
    (let [{person-name :name
           person-age :age} person]
      (print person-name)
      (print person-age))
Clojure doesn't natively support destructuring inside def and var though, the latter doesn't really exist in Clojure since there's not real local mutable variable, only local bindings. It supports it in let, function parameters, loop, and everything else that derives from those.

For fun, I just made a macro to support it in def as well:

    (defmacro ^:private def-locals
      []
      `(do ~@(for [local (keys &env)]
               `(def ~local ~local))))

    (defmacro ddef
      [destructuring value]
      `(let [~destructuring ~value]
         (def-locals)))

    (ddef [_ _ _ _ lamb] my-vector)
    (print lamb)

    (ddef {person-name :name
           person-age :age} person)
    (print person-name)
    (print person-age)
> are small CLI programs that help me free up mental space, and web apps. Clojure, outside of babashka seems ill-suited to the first

Ya, Clojure JVM is not ideal for small CLIs, because it has a slow startup. Though you can now compile a Clojure JVM program into a self-contained statically compiled native executable, it requires understanding a fair amount of GraalVM native-image, and Clojure infrastructure and all that to do so, actually Babashka itself is such a program. But that's also where Babashka comes in, because now all your effort learning Clojure also means you can reuse those learning for scripting and babashka driven CLIs, where as before you needed to rely on some other language for those, which meant having to learn something else for those use cases. You can also use ClojureScript to write Node.JS driven CLIs and scripts if you want, though I'd say Babashka is much nicer, unless you cared for some Node.JS library.

> And when I last tried to do web apps with Clojure, it seemed like everything relied on a lot of self-assembly, and didn't map cleanly to ways I knew how to web apps at the time from Go, C#,or Erlang. I dunno what the situation is there these day

It hasn't changed, familiarity is Clojure's enemy, that holds for frameworks as well. This is another learning curve.

> For Janet, I did lean into the more imperative constructs early on.

Ya, that will be another big learning curve then.


When I think of destructuring match, it's not just about destructuring, but also about matching a set of clauses against the matches.

I rely on the imperative parts of Janet less than I used to, but yeah.

I am surprised that no one has tried to build something like Sinatra for Clojure. From what I can see, nothing would keep Clojure from being able to make something like that.


> I am surprised that no one has tried to build something like Sinatra for Clojure. From what I can see, nothing would keep Clojure from being able to make something like that.

Sinatra is a framework rather than a library (Sinatra calls your code when requests come in rather than you calling the library code when requests come in [we call you VS you call us]) and so generally doesn't get a lot of mindshare in the Clojure community, where small, composable libraries are preferred.

Normally I see people using Ring (http request/response abstraction) + Compojure (routing) + Ring-compatible HTTP server (something like http-kit) for those purposes, and is what I normally use myself as well. Works well and once you've grokked the architecture of plugging those together, it becomes easy to hunt down issues and switch out parts whenever you want.


I assume you mean something like clojure.core.match https://github.com/clojure/core.match ? It is available, but not used that often.


> desktop GUI applications

With the exception of Electron apps, this is a bit of an exaggeration. There are far better choices in that area than Clojure.


That are also going to be close to Janet in look and feel? Please let me know, I'm interested to try.


Racket has a cross-platform GUI library. It’s what drives the DrRacket editor.


Oh nice, I did not know that, I've been wanting to try Racket for a while, maybe trying my hand at a desktop GUI will be it. Thanks.


I initially thought Janet was a language intended more for embedding inside C applications than general usage though. (If you’re intending to use the language for general purposes, there’s already too many contenders like Clojure and it probably has all the libraries you need for all sorts of things like webdev.) But given that there’s already a general-purpose package manager developed for Janet, I guess the dev is pivoting towards a different direction…

I’m still curious about how good the embedding story is (ex. for game scripting or as a configuration language), compared to other scripting languages like Lua/Squirrel/Wren. (Runtime memory usage, GC overhead, how easy and performant it is to create C bindings, etc…)


My impression of Janet is that it's threading a needle there.

The core is still heavily meant for embedding. It's not as light as Lua, but there's an amalgamation .c and .h, you can strip out everything that depends on an OS underneath, and so on.

That being said, a lot of folks that use Janet and are in the gitter/matrix server are currently focused on aspects of web dev, myself included. Lisp, as always, enables a lot of expressive power there that enables building your own pieces of the web stack, for example.

C bindings are relatively easy, and use the same interface that is used to build the standard library, so performance doesn't really take a hit from the interop code.

I can't speak to the GC, other than to say that it hasn't been an issue for me yet.


The other really pleasant aspect of Janet is that it easily enables build fat-image executables. Asset packing is as easy as a top-level

    (def asset (slurp "file.txt"))
This makes it a lot more at home on Windows than Python or Ruby on that respect, IME.


You forgot about GNU Guile which is an Scheme implementation designed specifically for embedding into applications.


I think guile has moved away from embedding, its most major use is probably in guix where much of the logic is directly implemented in guile with efforts (last i checked, a while ago though) to remove the remaining C++ and replace it with guile too.

It even has a JIT these days too


I initially thought it was Janet Airlines. https://en.wikipedia.org/wiki/Janet_(airline)


What's the package management story with Janet? I've had my eye on it, but I'm not willing to manually download a bunch of source repos and mess around with configuration in order to use other people's code. Life is too short for that stuff.


Using JPM (Janet Package Manager) has been really easy, in my experience. Add a list of dependencies to your project.janet file and you're good to go.


What languages do you use, and how is that heuristic going for you? I find package managers help for some time after they're introduced, and then life sucks once again as they enable people to add lots of dependencies. It's kinda like how building more roads never seems to fully eliminate traffic jams.


My main "professional" language is Python, but I have written at least toy programs (100-1000 LoC) in R, Javascript, Typescript, Ruby, Perl, Lua, Go, Crystal, C, Common Lisp, Racket, Guile, Idris 2, Haskell, Zsh, Bash, and POSIX-ish Sh, as well as written non-trivial AWK, Sed, and Jq scripts. Also Hy and Fennel to the extent that those count as separate languages.

Having a package manager means I can easily reuse other people's code. Not having a package manager means I can't.

Note that my use cases almost universally involve either "scripting" or developing low-volume server applications that are deployed in container of some kind, or on a VPS that I control.

I have only developed nontrivial standalone "libraries" in Python, and I only use Lua/Fennel for Neovim plugins (where I am not alone in bemoaning the lack of a standardized way to declare dependencies [1]).

In almost every program I have written, my priority has been on rapid development/prototyping. I want to be able to do things like parse JSON, make HTTP requests, perform lookups in tables/mappings/dictionary-like structures, and manipulate text.

I also have a day job, an amazing life partner, and more interests/hobbies than I have time left in my life to spend on them. For me, time spent fussing with manually copying source trees and reading through dense manpages is time I could be spending on other things.

I also come from the worlds of statistics and data science, where it is often said that "nobody cares about your code." Time spent reinventing tools is time not spent cleaning/exploring data, revising a report, building a dashboard to accompany said report, or trying out some new techniques.

So to answer the 2nd part of the question: it's going great!

[1]: https://neovim.discourse.group/t/plugin-metadata-formats-wha...


Ok, that makes sense. I see now how package managers are an unalloyed good for someone who's mostly or entirely doing prototyping work and doesn't have to do the work to constantly keep them up to date. When you're prototyping, it makes sense to be profligate with dependencies.


JPM is a tool distributed alongside Janet that will download deps for you with `jpm deps`.


I really like the neologism of “investingating”.


Oops, that was not an intentional spelling.


I figured, but I still like it! :)


I was hoping this story was going to be about flying Janet Airlines, the secret government airline that flies employees into Groom Lake (Area 51) and other secret and semi-secret installations in the southwest https://en.wikipedia.org/wiki/Janet_(airline)

Still a great article though


Did any other Old Folks click on this story thinkin it was gonna be about British JANet users who put the .uk first in their domain names?




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

Search: