Can the idea of concatenative programming languages be extended to call-by-need evaluation strategy?
I see some problems that I will explain with few examples. I will use a prefix instead of a postfix notation, so evaluation takes place from left to right.
Duplicate an argument seems trivial:
drop A B -> B
However, we can not forget that A is unevaluated, so the previous example only work for literals or quotations, but not for unevaluated code:
drop 123 B -> B drop "abc" B -> B drop [+ 1 2] B -> B drop + 1 2 B -> 1 2 B -- NON SENSE!
This can be solved in two ways. First, we can impose some structure (unflat) to the code based on the functions arity in the parsing stage:
drop + 1 2 B -> drop [+ 1 2] B // at parse time drop [+ 1 2] B -> B // at run time
The problem with this approach is that we lose all the benefits of concatenative programming (for example, flatness and multiple return values). In fact, we are converting function composition to function application in prefix notation... In this case:
dup + dup 2 3
Naively translates to:
dup [+ [dup 2] 3] -> dup [+ 2 2 3] // `+` arity mismatch. What to do with the extra value? Which one is it, 2 or 3?
But it result in an strict language is:
4 4 3
The second solution is to instruct the evaluator to reduce the stack just enough to get as many arguments as the current word needs:
drop + 1 2 B -> drop 3 B -> B
Looks good, but the target of lazy evaluation is to evaluate arguments only when needed. In this case we are evaluating
+ 1 2 needlessly. It is not a big problem until we found:
drop + bottom 2 B -> bottom
What contradicts the expected call-by-need semantics:
drop + bottom 2 B -> B