LIPS: Scheme based Lisp interpreter in JavaScript
lips.js.org
lips.js.org
What Clojure provides is a "loop" construct, which kind of looks like a limited form of self recursion.
They disliked that it didn't leave stack frames in some cases.
Apple refused to break the JS backward-compatibility guarantee and implemented as-per the spec and Chrome refused to turn on their implementation for users unless it added a special keyword to identify it as a tail call.
Chrome dev's emotional investment and resulting stubbornness has split the JS community and made their implementation incompatible with the spec. They are 100% in the wrong here.
You really undermine your position when you start fabricating bullshit like that.
The chromium team had good reasons to not implement it (including clusterfuzz security issues).
Apparently it was actually the Microsoft and Mozilla teams that blocked the feature: https://stackoverflow.com/questions/54719548/tail-call-optim...
There's nothing inherently insecure about proper tail calls and that is pure FUD.
That just leaves Google. They chose to die on the hill of syntactic tail calls and are still refusing to implement the spec. The only reason left is their pride and emotional investment in STC.
Even back then, Safari was implementing and had more marketshare than Edge and FF combined (now 2.5x their combined market share with FF only having around 3% share today).
I'm tired of bad excuses and want them to actually implement the now 8 year old spec properly.
- The only optimization that completely eliminates calls is inlining, so it is misleading. An optimized tail call still has some trappings of a call, like a control transfer somewhere else, with arguments.
- In Scheme lingo, the procedure calls so optimized are called tail calls; they are produced, and thus not eliminated. When the goto is taken, the procedure is said to be making a tail call, or tail calling. In assembly language, if you hand coded such a goto, you might put in a comment saying "this is a tail call", so it's not just Scheme lingo.
The "elimination" in "tail call elimination" refers to the "elimination" of the call frame.
In languages like C each call pushes a frame to the call stack. When the called procedure is done, execution returns to the site of the call. In a tail call there is no work to done, so the frame is now removed and execution continues where the previous frame points. But ... if we simply never omit the call frame, then executation then exectuation automatically returns to the parent frame.
The spec talks about allowing an "unbounded number of active tail calls". In an implementation with frames, it means tail calls can't just keep pushing frames.
Seen in isolation "tail call elimination" doesn't seem like a big deal, but combined with "real macros" (structural, scope-respecting macros) it is possible to implement control structures as a user without worrying about stack overflow.
The term "tail call optimization" still makes sense, but refers to the production of tail calls out procedure calls that occur in the tail position, not to the reduction/elimination of tail calls.
I made one attempt to implement them some time ago but failed. The code was not working. I based my implementation on someone else code. And later turned out that the call/cc that I based my code pm was not even full continuations and failed my test case.
Most Toy Scheme implementations don't have TCO and call/cc so I can't use them as a reference. But I have an implementation that is really promising that implemented them, the code is very minimal and I can base my code on that.
lips> (let ((a (begin (define b 4) 5)))
(+ a b))
9
lips> b
4
I mentioned this regarding a previous version and the author said it was deliberate. I feel it breaks lexical context and is icky in general. Scheme has definition context, and loosening it is probably a good thing - but not further than "to all bodies". > (define (fib x)
(let loop ((a 0)
(b 1)
(c 0))
(if (= c x)
a
(loop b
(+ a b)
(+ c 1)))))
> (fib 10)
55
> (fib 100)
354224848179261915075
> (fib 1000)
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875Even though Try Gambit is a REPL it's not very user-friendly.
- REPL history with up/down arrows (explained on the landing page's README)
- You can use the "help" procedure to browse the full documentation
- TAB key for autocompletion
- In addition to the REPL there is a code editor to edit programs stored in the browser
- The system supports multiple threads, one REPL per thread
- There is a builtin tutorial to learn the basics of Scheme with examples executable in the REPL with a single click on a "run" button
- The REPL supports single stepping the execution of code
- Easy interface to JS with the SIX syntax extension, e.g. (let ((msg "hello")) \console.log(`msg, 1+2/3))
- The error messages are clear and precise giving the file and line number and highlighting in yellow the piece of code (in the REPL or file) that raised the exception. Just try this in both systems to see the difference:
> (define (f x) (/ 1 (* x x)))
> (f 5)
> (f "hello") ;; the * procedure will raise an exception and highlight "(* x x)"- With LIPS you don't need any new syntax to integrate with JavaScript you just use JavaScript directly (let ((msg "hello") (console.log msg)) SIX syntax doesn't look like Scheme.
- Help popup documentation, with LIPS you have documentation directly in the REPL (when you hover over the symbol) and you can access docstring as strings and assign them to values.
- You can create new syntax similar to scheme vectors and make representation the same as code
- You can manipulate almost everything in the language like environments that you can modify
- LIPS show stack trace not one line which gives an error that makes it easier to find where the error happens.
- The Bookmarks allow to run the REPL on every page even PDF files with R7RS spec.
- You can actually run the code that uses quotes (’ The apostrophe symbol) from the spec if you use syntax extension to add new syntax.
> lips node:internal/modules/esm/resolve:853 throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
But this is Open Source and anyone who has a Mac can test it and fix this issue. I can't.