News for hackers. Stuff that matters.
Re:A Tour of Lisps by cmrdporcupine on Monday January 29, 2024 @04:55PM (#39183258)
While I agree with the author that there's something about Rust (my day-job lang & goto personal lang these days) that just lacks... elegance... I've also never found the Lisp (and many other FPs) emphasis on recursion all that compelling. Aesthetically or computationally. I find the programs written this way hard to reason about and read. Personally.

For me, the underutilized gold mine feels like logic languages (Prolog et al.) Though I know recursion is used there a lot, too.

Re:A Tour of Lisps by pfdietz on Monday January 29, 2024 @05:17PM (#39183525)
I don't think Common Lisp has an emphasis on recursion. As evidence, consider that it doesn't in the standard require any kind of tail recursion optimization and has multiple ways of doing iteration. Scheme is the language with the fetish about doing things with recursion.
Re:A Tour of Lisps by ctrw on Monday January 29, 2024 @08:09PM (#39185113)
Common lisp let's you do recursion when you need it and not when you don't. Using recursion for iterating through a list is stupid because you bury the lead in the middle of a function and you do the same thing until you stop. Using recursion for a tree or more connected graphs isn't because they are fundamentally recursive.
Re:A Tour of Lisps by xdavidliu on Monday January 29, 2024 @10:08PM (#39185961)
you can argue that lists are fundamentally recursive too: they are a car and and cdr, where the cdr itself is another list.
Re:A Tour of Lisps by cmrdporcupine on Tuesday January 30, 2024 @11:14AM (#39191869)
This is actually the core of what I was getting at. The way Lisp lists are constructed and processed actually encourages recursive methods.

But, again, it's probably because I've been exposed to too much Scheme that this sits so prominently in my mind. I've never worked in a large scale Common Lisp code base.

Re:A Tour of Lisps by pfdietz on Tuesday January 30, 2024 @12:21PM (#39192861)
Not really. You don't want to recurse down the tail of a list, as in practice that's a great way to overflow the stack.

In practice, you want to recurse only on the elements of a list. And that's just how you'd handle a tree whose nodes are vectors instead of lists. So the implication that lists lead to recursion is a red herring.

Scheme is misleading because of its fetish of implementing iteration with tail calls.

Re:A Tour of Lisps by soegaard on Tuesday January 30, 2024 @12:50PM (#39193233)
Btw - Racket guarantees more than Scheme. In Racket non-tail calls can't lead to a stackoverflow. When a "potential stackoverflow" is detected the "frames" are moved to heap, and the computation continues.

https://stackoverflow.com/questions/49912204/why-there-is-no...

Re:A Tour of Lisps by xdavidliu on Tuesday January 30, 2024 @02:02PM (#39194178)
> Not really. You don't want to recurse down the tail of a list, as in practice that's a great way to overflow the stack.

The SICP book had a paragraph on the distinction before recursive process and recursive procedure. I can't remember which was which, but the point there was that recursive procedure could be an iterative process if it's written with tail recursion in mind, in which case it would not overflow the stack.

Re:A Tour of Lisps by lispm on Tuesday January 30, 2024 @03:16PM (#39195151)
> actually encourages recursive methods

Not really.

Lisp has imperative loops which process lists: prog/go, do, dolist, loop for in/on.

Then it has a bunch of higher order functions: mapcar, map, mapc, member, find, remove, delete, every, some, ...

For example this is the equivalent of an early 1960s procedure to map a function over a list for side-effects:

    CL-USER 5 > (defun mapc-old (x f)
                  (prog ((m x))

                   loop

                    (when (null m) (return nil))
                    (funcall f (car m))
                    (pop m)

                    (go loop)))
    MAPC-OLD
As you can see above, the above uses a PROG providing a local variable M, a tag LOOP, and is using a GO statement to pass control to the position of the LOOP tag.

We can then process a list. Here we print the squares of the elements.

    CL-USER 6 > (mapc-old '(1 2 3 4)
                          (lambda (e)
                            (print (expt e 2))))

    1 
    4 
    9 
    16 
    NIL
Code using imperative loops to implement higher-order functions were common then. Then with macros one is hiding an imperative loop implementation behind a macro transformer: here the DOLIST macro

    CL-USER 12 > (dolist (e '(1 2 3 4))
                   (print (expt e 2)))

    1 
    4 
    9 
    16 
    NIL
Then one invented complex iteration sub-languages, here the LOOP macro in a tiny example.

    CL-USER 17 > (loop for e in '(1 2 3 4)
                       do (print (expt e 2)))

    1 
    4 
    9 
    16 
    NIL
Any list processing function, which is threatening to create stack overflows (even though some implementations can increase a stack at an stackoverflow error), is best rewritten into a function which does not cause stack overflows. Lisp uses traditionally imperative loop constructs for that. Scheme often uses tail-recursive functions for that.
Re:A Tour of Lisps by ctrw on Tuesday January 30, 2024 @08:13PM (#39198427)
Sure, the issue is that most loops are farily simple and putting the logic in the middle of a function far away from the iteration is bad.

It's the same reason why I use let instead of a function when I want to encapsulate simple state across multiple statements. Sure they are both the same fundamentally, but one is much easier to reason about.

Re:A Tour of Lisps by cmrdporcupine on Monday January 29, 2024 @07:18PM (#39184663)
That's fair. Any time I've decided to spend time in the sexpr-world, I've gone straight to the hard stuff, into Scheme. Apart from Elisp, anyways.

I've never worked in the source tree of a large Common Lisp program. Though I've bought the books and read the tutorials etc.

Re:A Tour of Lisps by mrkeen on Monday January 29, 2024 @05:52PM (#39183890)
> I've also never found the Lisp (and many other FPs) emphasis on recursion all that compelling.

Recursion appeals to me because of immutability:

I do not want to do a summation by writing the wrong answer to memory, and then updating it in-place until it's the right answer. It makes me have to think not only about what the right answer is, but when it is.

But can't I just use stack variables? Well, I don't think summation of primitive ints using stack variables is too taxing, but what about computing a word-count instead of a summation? Then your 'stack variables' are pointers into Strings and mutable HashMaps. When you return the result, do you make a defensive copy? How deeply do you copy? Your clone of a HashMap can still reference mutable data in your original HashMap.

Do you name your variable 'result' when it does not yet contain the result?

You might have to think about the primitive/object split - when is it pass-by-value and when is it pass-by-pointer? Do you have in-values and out-values? With immutability, you only have values, and you only pass-by-value.

If you're sharing objects across threads, and you decide to lock - is that sufficient? What if the caller takes the lock, does a get(), releases the lock, and then starts mutating the gotten value, bypassing the protection of the lock. Now you're thinking about defensive copies and maybe deadlocks too. Just share an immutable value without a lock.

Sometimes recursion is aesthetically better, but usually it's just what you're left with after you take a big hammer to mutability and its surrounding issues.

And most of the time (in Haskell but I'm guessing in Lisps too) you don't write out direct recursions, but instead use maps and folds which have recursion under the hood.

Re:A Tour of Lisps by galaxyLogic on Monday January 29, 2024 @06:36PM (#39184274)
I think mutable variables (inside functions) are just fine, as long as the function itself always returns the same result for the same arguments. That is the essence of "pure" I think.

A function should be a "black box". I don't care what happens inside it as long its externally observable behavior is pure.

Re:A Tour of Lisps by cmrdporcupine on Tuesday January 30, 2024 @11:18AM (#39191906)
Here's what I don't like about recursion & associated world of linked lists car/cdr: Modern Computing Hardware performance characteristics. That is, missed branch predictions, and lost opportunity to apply SIMD/vector operations.

On "classical" computing hardware, it's fine. On present-day architectures we're better off writing with "wider" operations across vectors rather than depth-first-ish recursive operations on scalars.

But yes, for some domains, the performance doesn't matter.

I just also find recursion more difficult to read in some contexts. In others, things like tree transformation etc. it's easier to read, for sure.

But I am also a fan of immutable, or at least copy-on-write, data, expressed in series of functional applications, etc. etc.

Re:A Tour of Lisps by chriswarbo on Thursday February 01, 2024 @08:26AM (#39215672)
Most of the time "recursing through a linked list" is still too low-level; and we can instead write our code at a higher level with `map`, `reduce`, etc. Recursion is then just an implementation detail of those functions; which we could instead swap out for, say, massively parallel fan-out across a datacentre (i.e. frameworks like MapReduce, and its descendents like Spark)!
Re:A Tour of Lisps by thuuuomas on Monday January 29, 2024 @05:03PM (#39183350)
Have you checked out any of the minikanren logic programming environments people have implemented in scheme?

The Reasoned Schemer is an accessible introduction to that space if you’re not put off by the socratic-themed dialogue.

& the Barliman demo is still pretty exciting even after LLM codegen.

https://youtu.be/er_lLvkklsk

Re:A Tour of Lisps by nerdponx on Monday January 29, 2024 @05:35PM (#39183705)
I'd like to see a practical use case for Minikanren other than quines. It's fun and interesting, but I'm not smart enough to go from "a tiny set of primitives" to "solving practical problems". I had trouble even implementing the basic "moses is a man" examples you see in Prolog tutorials.
Re:A Tour of Lisps by cmrdporcupine on Monday January 29, 2024 @07:16PM (#39184647)
Yeah me too I was just looking at an Elisp port (!) of minikanren the other day, and, yeah, all the examples were algorithmic puzzles, rather than solutions to problems.
Re:A Tour of Lisps by tmtvl on Monday January 29, 2024 @10:53PM (#39186217)
MiniKanren has been used in the medical world: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9562701/
Re:A Tour of Lisps by erichocean on Monday January 29, 2024 @09:54PM (#39185877)
It's also available in Clojure: https://github.com/clojure/core.logic

If you want to write one yourself, it's pretty easy: https://www.youtube.com/watch?v=y1bVJOAfhKY

Re:A Tour of Lisps by colingw on Monday January 29, 2024 @06:49PM (#39184400)
Manual recursion often isn't needed. You can get basically all of what you want from Transducers: https://github.com/fosskers/cl-transducers
Re:A Tour of Lisps by epgui on Monday January 29, 2024 @04:56PM (#39183269)
It’s like anything else, you get used to it just as easily.
Re:A Tour of Lisps by whartung on Monday January 29, 2024 @05:03PM (#39183355)
...or don't use it.

Scheme likes to tout it because of tail call optimization. Common Lisp can do it, but doesn't really shove it as a first principle of "Common Lisping" (and CL does not dictate tail call optimization).

I don't use it (save when it's necessary). I just iterate using the supplied iterators (do, dolist, loop, whatever).

I can't speak to other functional languages.

Re:A Tour of Lisps by ska on Monday January 29, 2024 @05:56PM (#39183927)
CL's iteration primitives are more capable that those provided by most languages, which often makes for a very clean approach.

Recursion is sometimes the easiest way to reason about a problem though, so it's nice to have decent support and TCO.

Re:A Tour of Lisps by epgui on Monday January 29, 2024 @06:52PM (#39184420)
> Recursion is sometimes the easiest way to reason about a problem though

Yes, it's one of the things that allow FP algorithms to be more declarative. It's difficult to seriously argue that recursion is fundamentally more complex-- the contrary argument is more obvious to me.

Everything that's left is the fact that it requires people to unlearn things, which is uncomfortable and requires effort, but unfamiliarity is not an indicator of complexity.

Re:A Tour of Lisps by cmrdporcupine on Monday January 29, 2024 @04:58PM (#39183291)
I take it as: mathematicians like recursion, so they put some recursion in your recursion...
Re:A Tour of Lisps by epgui on Monday January 29, 2024 @06:48PM (#39184394)
There's a strong argument to be made that mathematicians tend to build the simplest abstractions conceptually, especially in comparison to computer scientists. Personally, when I stumble upon some maths, I pay close attention. It has paid off.