From 58f22eb10b3912de06c75b41dfba323c72e0ef66 Mon Sep 17 00:00:00 2001 From: nddrylliog Date: Mon, 9 Aug 2010 01:54:22 +0200 Subject: [PATCH] Added chapter on tuples --- generics.md | 2 +- tuples.md | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 tuples.md diff --git a/generics.md b/generics.md index c8e0940..dc64fb2 100644 --- a/generics.md +++ b/generics.md @@ -1,5 +1,5 @@ Generics -======== +~~~~~~~~ Intro ----- diff --git a/tuples.md b/tuples.md new file mode 100644 index 0000000..3316be0 --- /dev/null +++ b/tuples.md @@ -0,0 +1,264 @@ +Tuples +~~~~~~ + +Intro +===== + +This section is rather long, and begins with an explanation of the practical +problem multi-return is supposed to solve. + +If you're just looking for reference material, you may jump directly +to the 'Multi-return using Tuples' section. + +Also, don't miss the last section on multi-variable declaration and assignment. + + +The problem +=========== + +How do we make a function that return several values? + +Using an array - minmax +----------------------- + +You can use an array: + + // counter-example: don't do that + minmax: func (list: List) -> Int[] { + min := INT_MAX + max := INT_MIN + for(i in list) { + if(i < min) min = i + if(i > max) max = i + } + + [min, max] + } + +But it's not practical, ie. if you want to retrieve min and max, you have to do: + + // counter-example: don't do that + result := minmax(mylist) + min := result[0] + max := result[1] + +We're using three lines only to retrieve results from a function. + +And what if minmax is changed to return only one value? The code will still +compile but fail on result[1]. + +Using a list of cells (ie. a Bag) +--------------------------------- + +Using an array doesn't allow different types, so + +Let's try using a list of cells: + + // counter-example: don't do that + meanAndTotal: func (units: List) -> List { + total := 0 + for(unit in units) total += unit weight + mean := total / units size() as Float + + [Cell new(total), Cell new(mean)] as ArrayList + } + +And to retrieve the values: + + // counter-example: don't do that + result := meanAndTotal(units) + total := result[0] get(Int) + mean := result[1] get(Float) + +Again, three lines, looks even uglier, no guarantees, not type-safe at +compile-time. Don't do that. + +Using references +---------------- + +And here's the closest we'll come to a tolerable solution without using +tuples: out-parameters. Let's rewrite the minmax example with it + + // counter-example: don't do that + minmax: func (list: List, min, max: Int@) { + min = INT_MAX + max = INT_MIN + for(i in list) { + if(i < min) min = i + if(i > max) max = i + } + } + +And to retrieve the values: + + // counter-example: don't do that + min, max: Int + minmax(mylist, min&, max&) + +Two lines is better, but what if we do: + + minmax(mylist, null, null) + +That's valid ooc, won't be caught at compile-time, but will sure as hell crash. +So it's not the perfect solution we're looking for. + +Multi-return using tuples - the solution +======================================== + +Multiple return types +--------------------- + +Tuples can be used to return multiple values from a function. Let's +rewrite our minmax function using that. + + minmax: func (list: List) -> (Int, Int) { + min := INT_MAX + max := INT_MIN + for(i in list) { + if(i < min) min = i + if(i > max) max = i + } + + (min, max) + } + + +Retrieving all values - multi-variable declaration +-------------------------------------------------- + +We can retrieve all values by using a decl-assign with a tuple +on the left and a function call on the right + + (min, max) := minmax(mylist) + +The tuple and the return type of the function call must match exactly, +any mismatch will result in a compile error. + +The tuple should only contain variable accesses - any other expression +will result in a compile error. + +The type of the variables declared inside the tuples are inferred +from the return type of the called function, just like regular decl-assign. + +There are ways to ignore some values, that are described in other sections. + + +Ignoring all but the first value +-------------------------------- + +In the minmax example above, we can retrieve only min if we want: + + min := minmax(mylist) + +It can even be used as an expression: + + "Minimum is %d" printfln(minmax(mylist)) + +Which leads to this rule: **when a function returning multiple values +is used as if it returned only one, the first value is used.** + + +Ignoring specific values - the '_' wildcard +------------------------------------------- + +What if we want only max? We can use '_' in place of a name, in a +multi-variable declaration: + + (_, max) := minmax(mylist) + +However, there is no way to use it as an expression, it has to be +unwrapped first, with a multi-variable declaration. + +For that reason, **it's good design to declare return values from most +interesting to least interesting**. + +The importance of return values order +------------------------------------- + +Take for example Process getOutput() in the sdk: + + getOutput: func -> (String, Int) {} + +The first returned value is what the process wrote to stdout, and +the second value is the exit code of the process. + +The function used to be declared like that + + getOutput: func -> String {} + +And didn't allow to get the exit code. Adding functionality didn't +hurt compatibility at all though - no code broke, because of careful +design. + +Be careful when designing APIs. Plan for growth. Listen to Guy Steele +(and his 'Growing a Language' talk) + +The '_' wildcard in greedy mode +------------------------------- + +We said above that the tuple and the return type of the function call +on either side of a multi-variable decl-assign should match exactly. + +For example, given this: + + plainWhite: func -> (Int, Int, Int, Int) { (1, 2, 3, 4) } + +The following lines are invalid: + + (one, two) := plainWhite() + (_, two) := plainWhite() + +Why? So that when incompatible changes are made to an API, you're +aware of it at compile-time, not at run-time. + +However, both these lines are valid: + + one := plainWhite() // as we've seen before + (_, two, _) := plainWhite() + +Although plainWhite() returns 4 values, a tuple with only 3 elements +works. + +**A '_' used at the end of a tuple will ignore every remaining return value** + +So that + + one := plainWhite() + +Is actually equivalent to: + + (one, _) := plainWhite() + + +Tuples beyond return - multi-declaration and multi-assign +========================================================= + +Using tuples on both sides of the decl-assign operator (:=) or +the assign operator (=) is valid. + +Examples: + + (x, y, z) := (1, 2, 3) + + (a, b) = (b, a) + +Swapping variables is valid, and should be supported by compliant +ooc compilers/runtimes. + + + + + + + + + + + + + + + + + +