News for hackers. Stuff that matters.
Re:ChrysaLisp by classified on Monday January 13, 2020 @08:34AM (#22034299)
Very confusing. It's not actually Lisp but some new language with superficially Lisp-like syntax. It doesn't say what it's for or why the hell anybody would want to use it.
Re:ChrysaLisp by lvh on Monday January 13, 2020 @10:07AM (#22035025)
What makes it "not actually Lisp but some new language with a superficially Lisp-like syntax"? (Never mind that it's ostensibly a lot more than just a language but an entire OS with a Lisp-geared runtime -- I'm assuming you're just referring to the language).

https://github.com/vygr/ChrysaLisp/blob/1808d2db54cdda378aae... sure looks like plausibly a Lisp dialect to me.

Is your objection to the execution VM (all the files ending in .vp)?

Re:ChrysaLisp by DannyB2 on Monday January 13, 2020 @10:36AM (#22035317)
1. no garbage collector

2. No tail recursion

3. There is no cons, cdr or car stuff. Lists are just vector objects and you use push, cat, slice etc to manipulate elements.

Only this much is radical enough to say that it isn't a Lisp but disguised in Lisp syntax. Lisp is as much about semantics as the syntax. The power of Lisp to do amazing things comes from the language capabilities. If you cannot reasonably translate powerful programs from Lisp textbooks, then is it a Lisp?

Some languages (example: JavaScript, Python) make it easy to create an inefficient Lisp interpreter that could execute common textbook programs. Minimax, A-star search, unification matching, etc.

Re:ChrysaLisp by lvh on Monday January 13, 2020 @10:40AM (#22035358)
Welp, you're right, all of those things are true for the lisp files too (not just vp). I just read some sample source and missed https://github.com/vygr/ChrysaLisp/blob/5483e0926b926c2cde63... . I agree those are pretty serious deviations that make "only superficially lisp-like" a reasonable statement. Thanks!

(IIUC the "garbage collector" is refcounting; I'm not sure what "All objects used by the Lisp are reference counted objects from the class library." means in practice, and specifically I'm not sure if that means "you basically have a GC in lisp as long as you don't make cycles".)

Re:ChrysaLisp by vygr on Monday January 13, 2020 @12:33PM (#22036556)
It means what it says ;)

All objects used by the Lisp are instances of classes from the class library, ie that stuff in class/ folder.

These instances are ref counted objects, as they are derefed and they drop to 0 refs they get deinited and freed.

The Lisp is constructed from these, for example Lists in the Lisp are an instance of the class/vector, numbers are an instance of class/num, the environment is a chain of class/hmap etc.

So yes, you do have GC if you don't make cycles, and you do have to care about not doing that just as you would in C++.

Re:ChrysaLisp by vygr on Monday January 13, 2020 @12:21PM (#22036439)
There is currently no GC, by choice, but I recently did some work to assist the Stats app that I added recently that could lead to me implementing a GC at some point in the future.
Re:ChrysaLisp by pjmlp on Monday January 13, 2020 @01:42PM (#22037239)
Lisp does not require tail recursion, only Scheme does.
Re:ChrysaLisp by mnemonicsloth on Monday January 13, 2020 @09:09PM (#22041155)
Most Common Lisps (certainly the compiled ones) do tail call optimization.

Clojure doesn't because it's a Lisp in Java, and Java doesn't. Back when Clojure was just starting to get some attention I watched a video [1] of Rich doing a demo for a user group. Tail call optimization was one of their first questions. He gave them an answer that sounded like he had given it multiple times before.

[1] can't find it, sorry.

Re:ChrysaLisp by lvh on Tuesday January 14, 2020 @10:59PM (#22051658)
> Clojure doesn't because it's a Lisp in Java, and Java doesn't.

I keep hearing this and I have no idea where it comes from. x86_64 doesn't either, Clojure has recur, and prefers consistent var semantics to silently specialcasing some tail calls. There's no particularly good reason the compiler couldn't just do it.

Re:ChrysaLisp by kazinator on Thursday January 16, 2020 @12:06PM (#22066457)
The JVM defines a particular model for program organization, which is largely inescapable from the point of view of someone targeting a language front-end to it.

x86_64 instruction set gives the programmer complete control over the organization of memory: the stack, use of registers, calling conventions and so on.

It has few safety features.

x86_64 doesn't specifically support tail calls, but it makes tail calls possible by way of jump instructions being able to go anywhere in the address space. An instruction in the middle of one function can branch to an instruction in the middle of another function, and without doing anything with the registers or stack.

The JVM byte code doesn't allow such a thing.

The JVM supports iteration. Therefore local tail calling is possible, because it's a syntactic sugar for local control transfers. That's presumably why Clojure can have recur.

Re:ChrysaLisp by lvh on Sunday January 26, 2020 @07:23PM (#22155886)
That sounds like a long-winded way of saying "x86_64 and the JVM are different but neither prevents TCO", which is precisely my point! Unless your point is "it's not TCO unless it's literally an instruction called 'jump' and iteration transforms don't count"?
Re:ChrysaLisp by lispm on Wednesday January 15, 2020 @12:07PM (#22055955)
For every function call it should not grow the stack and jump into the function. The JVM does not support that. One would need to compile the code in complex ways - function calls would no longer map directly to JVM instructions.
Re:ChrysaLisp by lvh on Wednesday January 15, 2020 @12:54PM (#22056464)
You're explaining TCO. I understand how TCO works.

I'm going to rephrase my own argument because it doesn't appear you interacted with it: what part of your argument doesn't work for x86_64? "One would need to compile the code in complex ways: function calls would no longer map directly to CALL/RET instructions." Sure! That's arguably what makes it an optimization. Who cares? CPUs can loop, and so can the JVM.

I would maybe see your point if Clojure didn't already know how to do tail calls (and so would need to implement the allegedly complicated compilation step), but as I have pointed out several times: it already has `recur`, which lets you call a function without the JVM thinking there is a function call going on.

Re:ChrysaLisp by lispm on Wednesday January 15, 2020 @05:16PM (#22059377)
Tail recursion optimization would be when the compiler on its own would recognize tail recursion calls and generate special code for that.

That one needs to manually annotate it in Clojure just means that the compiler is manually instructed to generate a loop-like construct instead of a function call.

The advantage in Clojure is that these loops are explicitly marked, which IMHO improves readablity.

Many Lisps don't bother to implement tail recursion optimization, because they support the more general tail call optimization - by adjusting/reusing the current stack frame and using a JMP instruction.

Re:ChrysaLisp by pjmlp on Tuesday January 14, 2020 @01:16AM (#22042316)
Yes it is a common optimization, however it is not a requirement for Common Lisp standard compliance, like it happens with Scheme.
Re:ChrysaLisp by rini17 on Monday January 13, 2020 @11:44AM (#22036023)
IMO closures and macros are absolutely essential, not just conses.
Re:ChrysaLisp by lvh on Monday January 13, 2020 @12:13PM (#22036357)
It ostensibly has both macros and closures, though. The lisp source i linked creates an anonymous function and uses it with map (which I assume means it has real closures though I guess you could fake that too), and here are some macros: https://github.com/vygr/ChrysaLisp/blob/bb4ad4116d6e794eb66e...
Re:ChrysaLisp by _pmf_ on Monday January 13, 2020 @09:05AM (#22034517)
> It's not actually Lisp but some new language with superficially Lisp-like syntax.

So, like Clojure.

Re:ChrysaLisp by DannyB2 on Monday January 13, 2020 @10:43AM (#22035404)
Why Clojure?

Clojure has cons, car, cdr which you can use in the classic Lisp sense. The only thing you CAN'T do is have a dotted pair or dotted list in the last cons cell.

One might argue that you cannot surgically alter data structures in Clojure. But you can! It's just that the function doing the alteration returns an entirely new data structure with the alteration in place -- but without the expected inefficiency of a copy operation. If you have an array of a billion elements, and alter one of the elements, you get back a new array with the alteration. But it is not a COPY of the entire array (with the expected time required to copy). The original array without the alteration also still exists -- but you don't have twice the memory usage now that there seem to be two slightly different arrays with these billion elements. Yet accessing or altering any element in the array has close to the performance you would expect of an actual array implementation.

Re:ChrysaLisp by kbp on Monday January 13, 2020 @06:26PM (#22039938)
> Clojure has cons, car, cdr which you can use in the classic Lisp sense. The only thing you CAN'T do is have a dotted pair or dotted list in the last cons cell.

So, it doesn't have them in the classic Lisp sense. Conses are just pairs. Using them as such isn't exotic. (cons 1 2) being an error in Clojure isn't a minor thing, it's very unique compared to other Lisps. It has a very different definition of cons.

Re:ChrysaLisp by DannyB2 on Tuesday January 14, 2020 @12:49PM (#22047217)
Yes. Clojure doesn't have cons cells in the traditional sense. But as long as you don't need dotted cdrs, you can "think" of using cons, car, cdr in the ordinary way. You can use existing lisp idioms of recursively taking apart, editing and rebuilding a complex form.
Re:ChrysaLisp by MadWombat on Monday January 13, 2020 @10:54AM (#22035498)
Tail recursion? As far as I know, Clojure doesn't and cannot have it because JVM doesn't support it.
Re:ChrysaLisp by bitwize on Monday January 13, 2020 @11:16AM (#22035695)
Kawa supports TCO with an optional flag. It uses trampolines to work around the JVM's limitations. This makes Kawa more akin to a real Scheme. (Still doesn't have full conts, only upward conts which are homeomorphic to Java exceptions.)

Clojure could have done the same, but Rich Hickey deliberately chose not to, instead providing a special 'rec' construct for tail self calls. But this doesn't make Clojure not a Lisp, it only makes it not a Scheme.

Re:ChrysaLisp by lvh on Monday January 13, 2020 @11:30AM (#22035830)
Clojure has `recur` that does what you want.

I'm not sure why it matters what the JVM supports. x86_64 doesn't do tail recursion either; it's the compiler's job to translate. Let's separate the `recur` vs the function name itself for a minute: if Clojure automatically took a function body with a tail-position recursion using the name of the function itself and translated it to a loop, would you agree that's tail recursion optimization?

Re:ChrysaLisp by DannyB2 on Monday January 13, 2020 @02:13PM (#22037547)
Recur is useful in Clojure, but still not a replacement for tail calls.

Imagine two routines that call each other. A calls B, which calls A, which calls B, and continues recursion until the real answer is calculated, then returns and unwinds the stack. With real tail calls, there is no stack expansion. When A calls B, the original stack frame of A is overwritten to be the new stack call frame for B, and then A does a JUMP to the right code in B which eventually will do the RETURN instruction (or recursively call A).

Re:ChrysaLisp by lvh on Tuesday January 14, 2020 @10:57PM (#22051651)
I agree that's an optimization and an important litmus test, but calling any TCO that doesn't also handle mutual recursion (but does naive fib just fine) not "real" feels like a stretch. At least we agree on the facts: if you want mutual recursion in Clojure you need to write it with letfn + trampoline. That feels like it's only a hop away from recur to me, as in: if recur doesn't feel like real TCO to you I imagine letfn + trampoline won't either, but letfn+trampoline means that I can write all the mutual recursion things I want to write almost exactly the way I want to write them, so it's close enough for me.
Re:ChrysaLisp by hajile on Monday January 13, 2020 @01:41PM (#22037234)
Common Lisp doesn't require tail-call support (though some implementations have it).
Re:ChrysaLisp by kazinator on Tuesday January 14, 2020 @02:44PM (#22048476)
Tail recursion can be cobbed together with macros. This is particularly effective for recursion among local functions in the same lexical scope:

http://www.kylheku.com/cgit/lisp-snippets/tree/tail-recursio...

The above tail recursion macros achieve local tail calls by transforming to a tagbody with go. This is offered in the form of a tlet macro whose syntax is like labels. Write your mutually tail recursive functions as labels, then change labels to tlet to try it with this.

tlet is based on argtags: a form of tagbody whose labels take arguments. These arguments perform a re-assignment of local variables from parameters that accompany the goto transfer.

Tail calls among top-level functions are supposed by a complementary facility called deftail which uses a combination of non-local dynamic control transfers and a dispatch trampoline.

Re:ChrysaLisp by DannyB2 on Monday January 13, 2020 @02:15PM (#22037561)
While tail call is an optional feature in CL, are there any (or very many) implementations that DO NOT have tail call elimination? (just curious)
Re:ChrysaLisp by holtalanm on Monday January 13, 2020 @11:16AM (#22035692)
don't they get around that by compiling the tailrec to bytecode as a while loop?

other languages supporting tail recursion on the jvm do that. I don't know enough about clojure to say that is how they do it, though.

Re:ChrysaLisp by DannyB2 on Monday January 13, 2020 @05:15PM (#22039320)
Tail calls aren't just for loops. Tail calls can be across functions.

A calls B calls C calls A calls B ... repeat until ... one of the functions solves the problem and does a return. Then the stack is unwound. But with TCO all of the tail calls simply re-use the current stack frame and JUMP to the next function.

Re:ChrysaLisp by lvh on Monday January 13, 2020 @11:31AM (#22035842)
Yes, but you have to be explicit that it's a recur by using the recur special form. (I'm not convinced that matters, but some people get hung up on it.)
Re:ChrysaLisp by holtalanm on Monday January 13, 2020 @02:10PM (#22037521)
yeah, there is special syntax for it in other jvm languages that support tail recursion, as well (kotlin).

i honestly don't know how they would do it otherwise, really. I actually liked the `recur` syntax of clojure when i was playing around with it a few years ago, though.

Re:ChrysaLisp by lvh on Tuesday January 14, 2020 @10:48PM (#22051620)
I don't see why the compiler couldn't be aware of the function it's compiling and special-cases that tail recursion; that's what every other compiler does.