You are viewing archived messages.
Go here to search the history.

Paul Tarvydas 2025-09-04 14:34:49

Pond'ring aloud:

We know that "loop" is recursion, but recursion is often expressed in too-academic a manner.

We know that recursion consists of 2 parts:

  • termination case
  • recursion case.

I'm thinking about what might be a less-inhumane syntax for expressing a recursive solution. Suitable for non-programmers and LLMs?

[aside: the goal is not "efficiency" at the machine level, but expressiveness and human (non-programmer) understandability]

humane syntax???:

----------------



break down member (x, list) -> ([#found | #not-found], value) {

    finish when list is empty { ^ #not-found, ɸ }

    finish when x in list     { ^ #found, list }

    decompose list' <- rest (list) {

        ^ again (x, list')

    }

}



break down append (x, list) -> value {

    finish when list is empty { ^ x }

    decompose «item» <- first (list), «list'» <- rest (list) {

        ^ prepend «item» onto again (x, «list'»))

    }

}
inhumane syntax:

----------------



(defun my_member (x lis)

  (cond ((null lis) (values nil nil))

        ((eq x (car lis)) (values t lis))

    (t (my_member x (cdr lis)))))





(defun my_append (lis x)

  (cond ((null lis) x)

        (t (cons (car lis) (my_append (cdr lis) x)))))

[aside: "send ..." sends something forward asynchronously instead of returning it synchronously to the caller and unblocking the caller]

less-inhumane syntax involving async ports:

-------------------------------------------



break down member (x, list) output ports: { success: [#found | #not-found], value: object } {

    finish when list is empty { send success: #not-found, send value: ɸ }

    finish when x in list     { send success: #found, send value: list }

    decompose list' <- rest (list) {

        ^again (x, list')

    }

}



break down append (x, list) output port: { value: object } {

    finish when list is empty { send value: x }

    decompose «item» <- first (list), «list'» <- rest (list) {

        send value: prepend «item» onto ^again (x, «list'»))

    }

}

suggestions / comments?

Kartik Agaram 2025-09-04 17:22:41

I don't really have a sense for what about the first syntax make it seem more "humane" to you. But FWIW, here's how Dijkstra (likely also "too-academic") would implement member and append , respectively:

i := list.lob;

do

  i <= list.hib and list(i) != x -> i := i+1

od

{i now describes where to find x, if valid}
list:hiext(x)

(At least as of 1977, Dijkstra doesn't seem to think about function boundaries at all.)

(Dijkstra recommended designing data structures out of arrays, in the way Lisp designs them out of cons pairs.)

When Leggett 2025-09-04 17:26:06

So in your language, would it be illegal to make a recursive call? Requiring the use of this special syntax?

Paul Tarvydas 2025-09-04 18:33:53

This isn't an attempt to banish recursion, only to capture its essence and give it a different (better?) syntax.

I am tinkering and brainstorming with ideas. I don't necessarily believe in this particular syntax, but, I do know that trying something , then critiquing it tends to move ideas forward...

IMO, the essence of recursion is: divide and conquer.

I am trying to do something that I thought would be simple, but am finding that it requires way more detail than I would expect anyone but a programmer would have the patience for. I have been trying to explain to an LLM (Claude 4.0) what I want, but, it keeps making blunders (due to my fault in not supplying enough niggly details, it gets really, really close, but always leaves out some important detailed edge-case).

Syntax-wise, I know that Loop is too low-level and leaves too many foot-guns lying around. Recursion is better, but too weird-looking. I'm wondering if there is a way to express the essence of divide-and-conquer in a less inhumane way. Maybe this needs multiple syntaxes? Or, maybe it just can't be expressed any better. We do see lots of attempts to define and improve on low-level loop, for, while, etc. syntaxes.

The Dijkstra thing smacks of too much low-levelness, esp. to someone who thinks that an array is just a list with its CDRs optimized away.

Kartik Agaram 2025-09-04 19:05:39

Programming is like a Necker cube. Each of us has a slightly different perspective on what is good and what is bad. The interesting question for me lately is: at what point should one give up on finding an axis of good-bad that everyone will get on board with, and instead focus on other aspects that seem more well-posed.

Paul Tarvydas 2025-09-04 19:18:50

I agree, with the nuance that there are multiple possible syntaxes / perspectives. There's what academic-analysts think is useful, there's what programmers think is useful, there's what non-programmers think is useful, etc. LLMs seem to come close to providing syntax for non-programmers, but, in this case, the LLM couldn't infer sufficient detail from my prompt. In other cases, I asked for smaller snippets and was very satisfied. I wonder what the "grain" size is for getting reliable results?

Kartik Agaram 2025-09-04 19:47:08

Absolutely, multiple perspectives. That's what I was getting at with the Necker cube analogy. None of them more right than the others.

But you often strike me as too attached to one perspective and ignoring the others. With any tool you're only going to get out what you put in.

Kartik Agaram 2025-09-04 19:49:44

Where I am: Recursion or iteration? Necker cube. Low level or high level? Necker cube. Academics or builders? Necker cube. Natural language or formal language? Necker cube. Correctness or friendliness? Necker cube. Companies or people? People, people, people.

misha 2025-09-06 11:07:31

Syntax is "rules". I argue that your "humane" samples have way more syntax than lisp ones.

You throw a lot of english words in there as a separate syntax element each.

This maybe would make my mom

1) think she kinda gets what this is about just because of familiar words

2) when she reads this.

But

1) I doubt she would actually understand what's that all about w/o further explanation

2) And certainly she would not be able to write anything in this syntax after seeing these 2 samples (I know because I can't).

Lisp's ~syntax~ shape is way closer to the actual "abstract shape" of the problem essence: base cases + recur case + branching (picking only 1 of them at time).

Lisp shape has fewer (but not 0) thing to question, e.g. are different parens mean different things? (yes they do, that's why vanilla lisp sucks to read and scared off so many people)

Specifically these lisp samples suck because nobody knows what the ** eq *t* *car* *cdr* *values* ** are. But those are names, not structure, not syntax. (ok, maybe values is somehow syntax, I don't know why it is there, what it does, and why would I care to have there anything other than yes or no )

Here is the clojure example (notice I redefined only 3 words, and honestly, I think true false defn would be ok w/o substituting them)

;; defn renaming needs to be done differently, via new defmacro, but it can be done globally once, and here it just illustrates the point: "keep the structure, use better names":

(let [NO  false

      YES true

      new-function defn]



  (new-function is-x-in-list? [x list]

    (if (empty? list)

      NO

      (if (= x (first list))

        YES

        (is-x-in-list? x (rest list))))))

It has the same structure as vanilla lisp samples, but much more "humane" (more widely recognizable names (to make 'em even more "humane" – maybe replace with chinese mandarin))

It explicitly has different types of words grouping: () and []

My mom would still have questions, but way fewer than in case of "humane" and vanilla lisp samples.

On the other hand, questions to the "humane" samples I have (to slightly better understand semantics, and maybe to try to write my own fns in this syntax):

  • why there are commas there, but not here?
  • what # ^ ɸ ' | -> <- are?
  • where does function name ends and arg name starts?
  • why some args are in () and some are inline?
  • I presume [] is a grouping around "regex or", and () - around return type? (try to explain it to my mom)
  • why «» in the second sample but not in first?
  • is break down a part of fn name or some keywords? why have them? what other options are? are those required?
  • if ^ is return indicator, why say finish ? if finish is return
    • why say ^ ?
  • ^ is in every {}, is it required? are those separate concepts, or is it just grouping syntax {^ ...} ?
  • would « and » be the same as " " or do I need to find them on my keyboard?
  • is ɸ – some constant? if yes, why not (because #found seems like a constant, and # seems like a way to mark constants). if not a constant, but some type (like value presumably is, can there be different ɸ ? (assuming that's an empty set. but we receive list (something sequential) and return set? huh.)
  • is )) in (x, «list'»))
    • a typo? or some syntactic thing?
  • there are commas , between words in { ^ #found, list } , but not right after ^ , is comma - separating args, and args are not in parens (see decompose and ^ ), but rest (list) seems like a function call, with parens... huh.
  • So if ^ is a fn call, how do I start another fn call after ^ (how do I stop the args)? Or is ^ – a syntactical keyword? Should it be be the last "line" in {} block? Why last decompose "line" in both samples is without ^ ? does {} "returns" ^ inside of it?
  • there are commas , between words in { ^ #found, list } , but not between 2 finish lines. why?

(and few more :) )