Open privat opened 10 years ago
For me: I would say to put parentheses everywhere (signature, return, call site).
Why? Because my eyes need parentheses at the call site to clearly parse heterogeneous constructs. It is for syntactic consistency that I would also put parentheses in the signature and return.
q, var r = div(7,3) # My eyes hurt. My brain tells me: what's that "q" doing there?
(q, var r) = div(7,3) # My eyes still hurt, but a little less. :)
# My brain tells me: why would this "r" declaration
# be valid outside the parenthesized scope?
# Yet, it's relatively normal for a declaration to span outside parentheses.
# Think of function parameter declarations that span to the function body.
As we discussed earlier with @privat, declaring and using vars in a multiple return seems a bit confusing for me.
e.g.
(var x, y) = div(5,2)
Were x and y declared here ? Is x the only one introduced and y was declared before ? Or are both declared here like what we'd have in Java or C ?
I'd say we go towards a 2-step approach; first declare the vars, then use them in the multiple return.
fun div(x,y: Int): (Int, Int) do return (x/y, x%y)
var x
var y
x, y = div(5, 2)
Bonus: Parentheses become nearly useless, but @egagnon seems to like them and I don't personally find them to be much of a burden in this case, even if it's not a very Nit-ey style of writing code.
Another solution is to inspire from Go, and have the variables used in a multiple return implicitely defined if not already present, though this might be confusing sometimes when modifying code in an already-existing method.
Go :
func SumProductDiff(i, j int) (int, int, int) {
return i+j, i*j, i-j
}
func main() {
sum, prod, diff := SumProductDiff(3,4)
}
Nit :
fun div(x,y: Int): (Int, Int) do return (x/y, x%y)
x, y = div(5, 2)
As for the more complex expressions as receivers of the multiple return, why not, but considering an AVarDeclExpr is a complex expression, It'd be hard to justify the decision of refusing them. I think that the use of temp variables to store the results and assign later is not that bad.
Besides if the variable is used later as a parameter to a method, the problem stays the same and a temporary variable will be used anyway.
Finally, for the Are the parenthesis mandatory for the return type of a method ?
, I'd say no too, this is quite readable even without them.
fun div(x, y: Int): Int, Int do return (x/y, x%y)
x, y = div(5, 2)
This seems readable enough for me. It seems though that most functional-inspired languages I know of (Go, Haskell, Lisp...) have decided that they're mandatory.
Note: If the grammar evolves even more, I'd vouch for the disappearance of parenthesis in the return !
fun div(x, y: Int): Int, Int do return x/y, x%y
x, y = div(5, 2)
Bye-bye parenthesis !
My eyes like the idea of not allowing for variable declarations on multi-return function call sites:
var x, y
x, y = div(5, 2)
I think that allowing for automatic variable declaration without the var
keyword would be a mistake. The idea of requiring the var
is not for parsing reasons, it is for catching programming errors.
One thing I do worry about is consistency, as it renders a language more intuitive. Apparently, Nit already supports the following syntax:
for key, value in map do
...
end
This does encode a non-parenthesized multi-assignation. It would then be inconsistent to require parentheses elsewhere. So, getting rid of multi-return variable declarations would solve at least two problems: inconsistent requirement of parentheses, and ambiguity of var x, y = div(5, 2)
.
So, my vote is for "no parentheses" anywhere (including function declaration, return, and call site), and for "no variable declaration" on multi-return call sites.
Note that I would still allow for variable declarations on single-return call sites to multiple-return functions (again, for consistency):
var x = div(5, 2)
Another solution is to use parenthesis to remove the ambiguity of variable declaration:
var x, y = div(5, 2) # declare x and y
(var x), y = div(5, 2) # declare x, but reuse existing y
x, var y = div(5, 2) # reuse x, declare y
I don't like the idea to forbid variable declaration with multiple return:
1) For the reason explained by Etienne on single-return declaration 2) Most language with multiple-return allow it 3) It takes to much lines for simple things:
var x
var y
x, y = obj.coords
In this case, programmers will use single returns because more elegant:
var x = obj.coords.x
var y = obj.coords.y
While it can be very neat with multiple returns and declarations:
var x, y = obj.coords
@Morriar suggested (var x), y = div(5, 2)
:
I like that.
I'm not that fond of the disambuiguisation via parenthesis, It's too cumbersome to use and a bit error-prone, it feels like the return of yet another structure like the writable that no one was able to use first try.
Yes, the two-step approach is indeed too long for such a simple operation, but it has the merit of being easy to read.
As for the languages that support a similar structure for multiple-returns, for what I know, they're either:
If someone knows of another language that has a solution to a similar problem, I'd be glad to hear it !
I kind of like the Go approach, but as @egagnon signaled, yes, the implicit declaration part is a bit dangerous.
Another possibility is that no reassignation is made when using multiple-returns.
Something like :
# x and y are declared here and can be reassigned later
# in anything other than a multiple-return.
var x, y = div(5, 2)
Though this would lack consistency, which I happen to like as well.
Why do you guys write:
var x
var y
x, y = obj.coords
Why not
var x, y
x, y = obj.coords
instead?
From what I know, as of today we can't chain declarations of variables on a single line, that has to be done in two statements.
But I might have missed a thing or two, anyone can confirm ?
OK. So, how about a trade-off that should cover 90+% of use cases?
var x, y = obj.coords # both x and y are being declared
----
x, var y = obj.coords # rejected: syntax error
Now for the 10-% use cases:
# x already declared
var y
x, y = obj.coords
----
# y already declared
var x
x, y = obj.coords
That would get rid of all parentheses, allow for simple syntax for common cases, and wouldn't overly complicate the syntax for mixed uses.
What do you think?
This looks like a good trade-off for my part !
I like the last proposal from @egagnon
nit does not have a syntax with many parentheses, so I like the idea to not introduce a new construction with that.
Plus, this is really simple
I did not commented on this (old) issue but +1 for the latest proposal of @egagnon
I like the proposal of @egagnon But, as the language swift, and in the way of OO of Nit, i see the functions with multiple return values like an object. ie, in the signature of our function, we could name our return values like:
fun div(a, b: Int): (div: Int, mod: Int) do return a/b, a%b
In fact, we could be stored both variables in a unique variable as:
var division = div(7,3)
and get return values like an object:
print "Division of 7 by 3: {division.div} modulus: {division.mod}"
@Tagachi seems like you will create a lot of disposable instance of your kind of tuple?
I do not really like anonymous tuples with named fields in statically typed languages as they are hard to expose, reuse, document or extends, that is somewhat contradictory with statically typed OO that encourages these things.
Such a tuple is analogous to a class, except that a class has a name, can have super-classes and can have methods
So let just try to expose the class instead!
class DivResult
var div: Int
var mod: Int
end
Then the div method can happily use it
fun div(a,b: Int): DivResult do return new DivResult(a/b, a%b)
Note that for the client, this change nothing except that things are more OO-clean.
var division = div(7,3)
print "Division of 7 by 3: {division.div} modulus: {division.mod}"
So one first issue is: is the addition of tuples useful? I do not think that in this case anonymous types really worth it as standard classes seem to solve the thing.
As said @Morriar one other issue with the approach is the mandatory allocation.
The last issue I see is mot philosophical: is this really useful with a real meaning?
I thing that the point about multiple return is when we have to return things that are not cohesive or not meaningful per se. Here, I do not see in which other cases the tuple (or the class DivResult) could be reused, even in the client code having a single variable to hold both the quotient and the remainder seems more an artifact of the programming language than a real need of the programmer or a real meaningful abstraction for the designer.
As an analogy, the same could be done with parameters (and is in fact done in some functional languages and some dynamically typed OO languages that does have a different philosophy than Nit).
fun div(data: (dividend, divisor: Int)): Int do return data.dividend/data.divisor
var data = (7,3)
var res = div(data)
Here, again, I do not this that the tuple used as an argument is that useful for the programmer. It may be useful for the language designer so that all function only need now to accept a single argument and that everything about multiple arguments is solved (delegated) trough tuples. Yes, this also allows the user to do some nice tuple shenanigans in a programmatic way, especially it also simplify some issue when meta-programming (or functional-programming). But again, does the additional complexity worth it?
Ping ? More and more I'd like to use this kind of structures, mainly for performance reasons.
The last example coming to mind is from the Codec class, and particularly its UTF-8 counterpart in my current refactor of the I/O library:
class Writer
var codec: Codec = utf8_codec is writable
fun write(s: Text) do
var b = codec.code_string(s)
write_bytes(b. items, b.length)
end
end
class UTF8Codec
fun code_string(s: Text): Bytes do
var b = new Bytes.with_capacity(s.bytelen)
b.append_text(s)
return b
end
end
One easy optimization the could be done here would be to directly return a NativeString instance from the passed Text if it is a FlatString along with its bytelen
, avoiding the creation of ephemeral instances here (which could add up badly if a lot of strings are written like in the compiler, where these manipulations are done 1.4M times).
So considering @egagnon's proposal was accepted as a good one, can we expect such a feature to be implemented in a (near?) future @privat ?
Ping ? This would be a fun addition to the language :)
I don't know if it's to late but personally I prefer this syntax :
x, y = div(5, 2)
It seem very simple and intuitive to use! And I think this write is closer to the script syntax language.
For the definition site, things seems straightforward.
The only question is about parentheses. In the current grammar, parentheses are mandatory in the return. But do we still force them to be mandatory in the signature
foo(a,b:Int): (Int, Int)
?For the client site, things become quite complex.
Do we force parenthesis in left-values?
Do we allow any kind of left-values? Especially when each left value in a multiple-return can have a distinct nature.