Eslisp – An S-expression syntax for ECMAScript/JavaScript, with Lisp-like macros
github.com
github.com
Is it possible to make externs or definitions of library functions? How much time does it take to take a fresh library and write those externs to access them in Parenscript land? And what limitations did you run into?
Cheers.
Is the implication here that people build on top of ecosystems because they're incapable of implementing things themselves in plain JS, and that lisp will make them capable?
That's not the reality I see as a developer. The team I work with are entirely capable of building all the things we need, but we build on an existing ecosystem of open source code because that way we can do more with the available resources. Ecosystems are as much about efficiency as they are about ability.
So you never have to try and follow the moving target of an ever evolving ecosystem. You just pick a language version and build your own with surprising ease. The amount of time you save from breakages upstream is amazing and something no one talks about.
Not exactly, but Lisp (specifically Common Lisp but also other lisps) will make far easier to implement whatever is missing thanks to the various features that enable very quick development, quicker than JS or Python. And interfacing with C code is also easy too. Interfacing with Java libs, even easier (using Armed Bear Common Lisp.)
(It's a lisp for both JS and Lua.)
Eslisp is 2015, which is around the same time that Lumen was started: https://news.ycombinator.com/item?id=10264970
Though from the commit history, Lumen was actually started in 2012. Regardless, both projects seem to be roughly the same idea, which I find very interesting.
"block" is better named "do", and "lambda" is better as "fn", but those are minor details. Most of the power of the idea seems present.
You're probably right I should have called function expressions fn. You can alias it with (macro fn lambda) though if you prefer that.
First I hear of Lumen! What a coincidence that they started at the same time. It seems their language is an "overlay" entirely separate from both Lua and JS which only runs at compile-time, kind of like Lua is to Terra http://terralang.org/. I think that means Lumen can't load macros from JavaScript modules at compile-time like eslisp can, but I'm not sure. I'll do some reading. Thanks for the link.
I wonder how their reasoning went. I find it generally confusing, if two different languages have similar syntax (why did Java have to look a bit like C?). I guess, I need a visual hint reminding me of the expected semantic.
Anyhow, even if the original JavaScript (perhaps "ScriptingScheme" or "WebScheme"?) would have more resembled Scheme, I doubt it would have used S-expr for long. Over the years I developed the needed parenthesis-blindness, but can't imagine most Web developers having that patience.
So I guess the goal is to be a purpose-designed, fairly well specified, only somewhat buggy, slower-but-not-debilitatingly-so implementation of 80% of Common Lisp. :)
So, does it include bit-arrays, custom hash tables, multimethods, method combinations, the condition-restarts signalling system, ability to place items on the stack instead of on the garbage collector, type declarations that enable speed optimizations, saving the running state to disk, and upgrading the instances of a class to a new class?
I don't want to be smug, but 80% is a bold claim. Again, I think Eslisp is useful and maybe i'll try to introduce it to my team (they're all JS/TS developers) to see if they get the GATEWAY DRUG to Lisp.
Yes, but Eslisp doesn't even implement 10% of Common Lisp...
Not that eslisp isn't useful. I think it's useful!!
It would be quite difficult in the general case to keep JavaScript formatted the same after it's been munged into S-expressions and converted back again. It would work the same, just look different. And the comments would disappear!
However, there has been some work into making a spec for a JS Concrete Syntax Tree (as opposed to Abstract Syntax Tree) which would retain whitespace, comments, and other such concrete "junk" that an AST typically abstracts out. https://github.com/estree/estree/issues/41; https://github.com/cst/cst It would be super cool to turn up at work in a JS shop armed with Emacs, and writing JavaScript at lightning speed using macros and structural editing, without anyone noticing.
It's a good idea. I'll make a note of it.
Edit: Is fixed.
I'm not against the concept of lambdas (obviously, I love them since I made a scheme dialect with them for video editing). The word today is too specific for a plethora of subtly different representations/implementations of the "lambda" of "lambda calculus".
If "lambda" is supposed to mean "function", choose "function". No harm done.
Anonymous function:
(fn [a] ... a)
And lambda:
#(... %)
I don't think this is official nomenclature, but I've been using that to distinguish between both syntaxes.
In truth though, anonymous functions is the more accurate terminology, because lambdas are actually not functions, they're supposed to curry and use term substitution. Where as most programming languages use anonymous functions, which support multi-arity and sometime even variadic arity, and don't use term substitution, but more common argument binding.
(map (fn first-or-last [x]
(or (first x) (last x))
my-seq)
(Of course the name has no semantic effects as you can't refer to it from code, but it's there in debugging / stack traces and serves as a comment for the reader).(I spent a bunch of time trying to sort this terminology when writing the book.)
That said, I don't really know if I'd agree that it's archaic, just less common. Python being an extremely popular language that uses it explicitly (as you say) makes it hard to really suggest that it's archaic.
First paragraph here uses both. I suspect that they use closure throughout the rest of the article, but the fact that they feel the need to reference the other name shows that lambda is still in common currency.
There are 2 because JavaScript has 2 different structures for making functions; function expressions (which I chose "lambda" for) and function declarations (which I chose "function" for). See https://github.com/anko/eslisp/blob/master/doc/basics-refere... for examples.
Function declarations are statements, and you need to provide a name for the function.
Function expressions are expressions, and the name is optional.
In practice they often end up being interchangeable, but I just wanted to make sure any JavaScript is definitely representable.
Show HN from 2015: https://news.ycombinator.com/item?id=10264970
(macro what? (function () (return '(a b c))))
(what?)
That compiles just fine to the JavaScript code a(b, c);
It does try to validate identifier names during code generation though (using the esvalid module), so if you give it (? 1 2)
then it will error with [Error] Identifier `name` member must be a valid IdentifierName
At line 1, offset 1:
(? 1 2)
(In a terminal, the offending character is highlighted.)It can be surprisingly fun and convenient to have the full macro power of Lisp layered on top of these dynamic languages.
(or if it's because of Numpy, see NumCL https://numcl.github.io/numcl/)
in my taste Hy lacks a good lot of Lisp advantages… no closures with "let" by default, no image-based development and the excellent Lisp REPL, no Lisp's interactivity (in Hy or Python a process must restart after a code change, whereas in Lisp you compile one function with a keystroke and voilà, it's here to test), no Lisp's efficiency, no Lisp object system, etc. https://lisp-journey.gitlab.io/pythonvslisp/
"I wanted JavaScript to be homoiconic and have modular macros written in the same language. I feel like this is the adjacent possible in that direction. Sweet.js exists for macros, but theyre awkward to write and aren't JavaScript. Various JavaScript lisps exist, but most have featuritis from trying too hard to be Lisp (rather than just being a JS syntax), and none have macros that are just JS functions"
https://github.com/anko/eslisp/blob/master/doc/comparison-to...
But still I think that `((. console log) "hello")` is not as nice as `(console.log "hello")` as in LIPS. My old code was using the same syntax but I've changed the interpreter and you now can use dot notation on any JavaScript objects.
I'm happy that eslisp has inspired other language ideas. LIPS is cool. The auto-resolving Promise feature is a great idea that's only really possible when you implement the language as an interpreter like you have. Generated code for that would be a mess...
I agree about the (. console log) thing. Such a common operation should be easy to type. I only did it this way to avoid adding any more syntax to the language, because I thought I could solve that problem later by making the syntax configurable by users.
...which I haven't really done well yet. Eslisp has a system called _transform macros_, one of which is https://github.com/anko/eslisp-propertify which turns `console.log` atoms into `(. console log)`, making the syntax legal. The transform macro system is really awkward to use and limited in functionality. I'm working in private on replacing the whole parser with one that user macros could modify, based on https://github.com/anko/partser. Parser combinators are the future!
Finding traction for a new language is difficult. It requires quite a large mass of work, documentation, and polish, before people view it as stable enough to invest their time in.
For now, I'm happy even if eslisp is viewed as a convoluted experiment. If it dies, perhaps like I found Wisp and had ideas, someone will find this and have ideas. Projects die, progress continues.
"ClojureScript tries to be Clojure, and Parenscript tries to be Common Lisp. Eslisp tries to be JavaScript; it's intended to just be an obvious one-to-one syntax replacement.
I wrote a brief comparison in the docs: https://github.com/anko/eslisp/blob/master/doc/comparison-to... "
https://news.ycombinator.com/item?id=10266254
I am definitely appreciating eslisp's almost one-to-one mapping to JavaScript. Finally I can program JavaScript with macros! I've searched for something like this before but didn't find it unfortunately.
I used sweet.js in the past to implement Go-style channels in JavaScript so that I could handle complex asynchronous semantics (including buffering) more elegantly.
Well, this is actually a very good idea!