3 ms·
> a computation (a “function”) must either return a value or hang/crash. It can also simply return control, without returning anything. It's equivalent to invo
by Joker_vD 2y ago
> a computation (a “function”) must either return a value or hang/crash.
It can also simply return control, without returning anything. It's equivalent to invoking a continuation with zero arguments. Do you allow for zero-argument functions, at least?
Of course, if a function can't return no value whatsoever, you suddenly need new syntactical categories to support it: you need to prohibit using such functions in an expressions (only call statements are allowed), you need a way to return from such a function (naked "return", which is prohibited from taking any expressions), and it's also severely strains your generics/templates because you can't treat such functions uniformly etc.
- lmm 2y ago> It can also simply return control, without returning anything. It's equivalent to invoking a continuation with zero arguments. Do you allow for zero-argument functions, at least? There's nothing weird about a function taking 0 arguments, any more than there's anything weird about a function taking 3 or 5 arguments. A function can't take an argument of type void, so functions shouldn't be allowed to return void either. > Of course, if a function can't return no value whatsoever, you suddenly need new syntactical categories to support it: you need to prohibit using such functions in an expressions (only call statements are allowed), you need a way to return from such a function (naked "return", which is prohibited from taking any expressions), and it's also severely strains your generics/templates because you can't treat such functions uniformly etc. Or you do what sensible languages do: all functions return a value (if they return at all), functions that don't want to return anything in particular can return a unit value but that value is just a normal value that behaves normally in expressions etc., you don't make naked return a special case (you can make it syntax sugar for "return unit" if you really want), and you can treat those functions uniformly in generics, much more easily than with C++ templates where you have to make special cases for void functions.
- Joker_vD 2y agoI don't argue with any of that, you know. But we do have a historical (stupid in retrospect) split between functions and procedures, dating a-a-a-all the way back to ALGOL 60 at least.
- shiandow 2y agoYou encounter the same problem with zero argument functions. A function on a zero type would be unable to return any value, since there's no value you could apply it to. To have a function of zero arguments you should use the unit type (which means the function effectively picks out a single value). This is also related to how a function with multiple arguments is a function of the product type, and an empty product is 1 not 0.
- Joker_vD 2y agoNo, you just invoke the function and pass it zero arguments, that's it. Sure, you can build your whole theoretical framework of computation with only the functions of exactly one argument, and then deal with tuples to fake multi-valued arguments/multiple return values — but you don't have to do that. You may as well start from the functions with arbitrary (natural) number of arguments/return values, it's not that hard.
- shiandow 2y agoSure, and passing it zero arguments is exactly what it means to evaluate it on the single value of the unit set. I mean surely we can agree that a pure function of 0 arguments picks out exactly 1 value, and that a function that accepts n different values (values not arguments) as input returns at most n different results? Why make an exception for n=0? Your definition of a function of 0 arguments and that of a function over the unit set are identical. Or at least equivalent.
- Joker_vD 2y agoThey're equivalent, but only up to whatever computational substrate one is actually using. You can build functions out of small-step operational semantics of, say, a simplistic imperative register machine with a stack. In this case, a function of 0 arguments and a function of 1 trivial unit argument are visibly different even though their total effect on the state is the same. After all, we're talking about theory of computation and so it better be able to handle computations as they are actually performed at the low level, too. It's yet another example of "in theory, the theory and the practice are the same; in practice, they're different": I have written a toy functional language that compiles down to C, and unit-removal (e.g. transforming int*()*int into int*int, lowering "fun f() -> whatever = ..." into a "whatever f(void) {...}" etc.) is a genuine optimization. The same, I imagine, would apply to generating raw assembly: you want to special-case handling of unit so that passing it as an argument would not touch %rdi, and assigning a unit to a value should not write any registers, and "case unit_producing_function(...) of -> ... end" actually has no data dependency on the unit_producing_function etc.