Comment by anentropic
8 months ago
I remember seeing HVM on here a year or two back when it came out and it looked intriguing. Exciting to see something being built on top of it!
I would say that the play on words that gives the language its name ("Bend") doesn't really make sense...
https://github.com/HigherOrderCO/bend/blob/main/GUIDE.md
> Bending is the opposite of folding. Whatever fold consumes, bend creates.
But in everyday language bending is not the opposite of folding, they are more or less the same thing. Why not "unfold", which also has a connotation of "the process of happening" as well as merely the opposite of folding?
I have a question about the example code and output for bending:
type Tree:
Node { ~lft, ~rgt }
Leaf { val }
def main():
bend x = 0:
when x < 3:
tree = Tree/Node { lft: fork(x + 1), rgt: fork(x + 1) }
else:
tree = Tree/Leaf { val: 7 }
return tree
tree = fork(0)
tree = ![fork(1), fork(1)]
tree = ![![fork(2),fork(2)], ![fork(2),fork(2)]]
tree = ![![![fork(3),fork(3)], ![fork(3),fork(3)]], ![![fork(3),fork(3)], ![fork(3),fork(3)]]]
tree = ![![![7,7], ![7,7]], ![![7,7], ![7,7]]]
Where does the initial "tree = fork(0)" come from?
`bend` is a convenience syntax that "just creates an in-place recursive function, immediately calls it with an initial state, and then assigns the end result to a local variable" ... "in a single statement, rather than needing to name a separate external auxilliary function that you'll only use once", according to the original author on Twitter: https://x.com/VictorTaelin/status/1791964640533958924 and https://x.com/VictorTaelin/status/1791996185449791932
But contrary to this, I think explicitly separating function declaration and function calling, in the following kind of syntax, would make it much clearer and less complected where the initial condition `tree = fork(0)` comes from. In the original example it came from `bend x = 0`, but here the function declaration is separate and the call more explicit: so it more obviously comes from `createTree(0)`:
Besides not needing a local variable `tree` here, the unique thing here is the elimination of the else-clause, to reduce unnecessary nesting, and a rule that the language just early returns the last result of any nested condition. If it doesn't go into any nested condition, then it just returns the last result in the main function body (like Ruby). Without any `return` keywords needed in either case. Wouldn't this be quite beautiful?
Re: name. Fold and bend are indeed called fold and unfold in Haskell and traditional functional programming literature.
I wonder if bend has to do with how we manipulate the computation's interaction graph while evaluating a bend. There might be some bending of wires!
Re: code example
In the code example, x=0 is the seed value. tree = fork(0) must mean "fork off to evaluate the bend at the seed value". In that first fork, we fork twice with the value x=1, to get the left and right subtrees of the top level node. We then fork four instances of x=2, eight instances of x = 3, and finally get our balanced binary tree with eight 7s.
Note this is guesswork. I don't know what the ![a, b] syntax means, and I haven't read much of the guide.
Appendix: Notes on Fold Vs Bend
I wrote these for an earlier draft while reminding myself about these operations. I include them more for my benefit, and in case they help you or the audience.
Fold and bend are categorical duals, aka catamorphisms and anamorphisms. One takes a monadic value and reduces it into an ordinary value. The other takes an ordinary value and expands it into a comonadic value.
Fold starts with a value in an inductive data type, and then replaces its constructors with a function. For example it takes a list (1:2):3, and replaces the constructor : with the function `+`, to get (1+2)+3 = 6
Bend starts with a seed value and a function taking values into constructor expressions for a conductive data type. It then grows the seed into a potentially infinite AST. For example the seed value 1 and the function f(x:xs) = (x+1) : (x:xs) gives us the infinite lazy list [1, 2, 3, ...]
The question that comes to me is: can I use fork(x) outside of a bend?
Seems like probably not, there doesn't seem to be enough information in the 'argument' to this 'function' to do anything useful without the implicit context of the bend construct.
For that reason I think I'd prefer it if fork was a keyword (like 'bend' and 'when') rather than a 'function', just at the surface syntax level to give a clue it is something special.
I guess fork is a kind of 'magic' function that represents the body of the bend. It's a bit like a 'self' or 'this'.
At the moment this syntax is in a weird half-way point ...the underlying concept is necessarily functional but it's trying to look kind of like an imperative for-loop still.
I wonder if we couldn't just explicitly create a 'bendable' recursive function that can be 'bent' by calling it. But I guess it's like this because it needs to be tightly constrained by the 'when' and 'else' forms.
TBH the more I look at this example the more confusing it is. The other part I wonder about is the assigning of new values to tree var... can I set other local vars from outside the bend scope? I don't think so, I guess it'd be a syntax error if the var names assigned in the 'when' and 'else' clauses didn't match?
Again it's sort of overloading an imperative-looking syntax to implicitly do the 'return' from the implicit recursive function.
Later on there is this example:
And here I wonder - does 'width' have a value after the bend? Or it's only the last assignment in each clause that is privileged?
That's an odd mix in a language which otherwise has explicit returns like Python.
If so I wonder if a syntax something like this might be clearer:
i.e. name the return var once in the bend itself, yield intermediate values (to itself, recursively) and return the final state.
The first `fork` is from using bend and passing the initial state
I would have described the logic in the exact same way, but I still don't see where initial tree = fork(0) state comes from
all the other "fork"s in the output are produced explicitly by:
well, it seems fork is some kind of builtin function that is intimately related to the bend construct: https://github.com/HigherOrderCO/bend/blob/main/FEATURES.md#....
so presumably the initial fork(0) state is implicitly produced by the bend