www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Tuples citizenship

reply bearophile <bearophileHUGS lycos.com> writes:
I think std.typecons.Tuples merit to be a little more citizens in D and Phobos.
I think reducing the usage of "out" argument, and replacing them with a tuple
result, will avoid mistakes and make the code look better. In std.math there
are functions that maybe are better to use std.typecons.Tuple:

pure nothrow  trusted real frexp(real value, out int exp);
==>
pure nothrow  trusted Tuple!(real, int) frexp(real value);


nothrow  trusted real remquo(real x, real y, out int n); 
==>
nothrow  trusted Tuple!(real, int) remquo(real x, real y); 

------------------------

It's good for tuples to become more common in D code. Some time ago I have
asked the built-in associative arrays to grow a method to iterate on key-value
pairs, named "byPair":
http://d.puremagic.com/issues/show_bug.cgi?id=5466

Andrei answered:
byPair is tricky because std.tuple is not visible from object.

How do you solve this problem? Now I think about a "pairs" method too: int[string] aa; Tuple!(string, int) ps = aa.pairs; foreach (p; aa.byPair) assert(is( typeof(p) == Tuple!(string, int) )); This example shows an use case of byItem. Given a string, the task here is to show the char frequencies, putting higher frequencies on top, and sorting alphabetically the chars that share the same frequency. A Python 2.6 program that solves the problem: from collections import defaultdict text = "the d programming language is an object oriented " + \ "imperative multi paradigm system programming " + \ "language created by walter bright of digital mars" frequences = defaultdict(int) for c in text: frequences[c] += 1 pairs = sorted(frequences.iteritems(), key=lambda (c,f): (-f,c)) for (c, f) in pairs: print f, c The output of the Python program: 20 14 a 12 e 11 g 11 i 11 r 10 t 9 m 6 n 5 d 5 l 5 o 4 p 4 s 3 b 3 u 2 c 2 h 2 y 1 f 1 j 1 v 1 w Three different solutions in D (probably there are other solutions, maybe even better ones) using Phobos: import std.stdio, std.typecons, std.algorithm, std.array; void main() { auto text = "the d programming language is an object oriented " ~ "imperative multi paradigm system programming " ~ "language created by walter bright of digital mars"; int[char] frequences; foreach (char c; text) frequences[c]++; Tuple!(int,char)[] pairs1 = array(map!(c => tuple(frequences[c], c))(frequences.byKey)); schwartzSort!(p => tuple(-p[0], p[1]))(pairs1); foreach (pair; pairs1) writeln(pair[0], " ", pair[1]); writeln(); import std.conv; dchar[] keys = to!(dchar[])(frequences.keys); schwartzSort!(c => tuple(-frequences[cast(char)c], c))(keys); foreach (dchar c; keys) writeln(frequences[cast(char)c], " ", c); writeln(); Tuple!(int,char)[] pairs1b = array(map!(c => tuple(-frequences[c], c))(frequences.byKey)); sort(pairs1b); foreach (pair; pairs1b) writeln(-pair[0], " ", pair[1]); writeln(); } A version using AA.pairs (or array(AA.byPair)), I have not used 'auto' for type clarity: Tuple!(char,int)[] pairs2 = frequences.pairs; schwartzSort!(c_f => tuple(-c_f[1], c_f[0]))(pairs2); foreach (c_f; pairs2) writeln(c_f[1], " ", c_f[0]); I am now writing a good amount of D2 code, some of it is functional style, or it's just translated from Python, and one annoying thing that comes out quite frequently is the lack of syntax to unpack a tuple into some variables. If you want a short self-contained example, this is a small program (and it's not much functional. Tuples aren't just for functional-style code): http://rosettacode.org/wiki/Sokoban#D It contains code like: immutable dirs = [tuple( 0, -1, 'u', 'U'), tuple( 1, 0, 'r', 'R'), tuple( 0, 1, 'd', 'D'), tuple(-1, 0, 'l', 'L')]; // ... foreach (di; dirs) { CTable temp = cur; immutable int dx = di[0]; immutable int dy = di[1]; With tuple unpacking becomes: foreach (di; dirs) { CTable temp = cur; immutable (dx, dy) = di.slice!(0, 2); I don't know if this is going to work, because di[0..2] creates a 2-typetuple! foreach (di; dirs) { CTable temp = cur; immutable (dx, dy) = di[0 .. 2]; Maybe this is not efficient: foreach (di; dirs) { CTable temp = cur; immutable (dx, dy, _1, _2) = di; Another example from that little program: alias Tuple!(CTable, string, int, int) Four; GrowableCircularQueue!Four open; // ... while (open.length) { immutable item = open.pop(); immutable CTable cur = item[0]; immutable string cSol = item[1]; immutable int x = item[2]; immutable int y = item[3]; With a tuple unpacking syntax becomes: while (open.length) { immutable (cur, cSol, x, y) = open.pop(); While I use tuples I hit similar situations often. For people interested in trying this idea, there is a patch by Kenji Hara (one or two parts are missing, like tuple unpacking in a foreach(...) and in function signatures, but I think most meat is already present): https://github.com/D-Programming-Language/dmd/pull/341 Bye, bearophile
Mar 01 2012
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, March 01, 2012 17:08:44 bearophile wrote:
 I think std.typecons.Tuples merit to be a little more citizens in D and
 Phobos. I think reducing the usage of "out" argument, and replacing them
 with a tuple result, will avoid mistakes and make the code look better. In
 std.math there are functions that maybe are better to use
 std.typecons.Tuple:
 
 pure nothrow  trusted real frexp(real value, out int exp);
 ==>
 pure nothrow  trusted Tuple!(real, int) frexp(real value);

Having good tuple support is great, but out parameters are great too. I'm sure that there are plenty of cases where using out parameters is actually far cleaner than using tuples, since you don't have multiple return values to deal with. So, better enabling tuples is great, but I don't think that we're necessarily moving in a good direction if we're trying to eliminate out parameters in favor of tuples. - Jonathan M Davis
Mar 01 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 01/03/2012 23:33, Jonathan M Davis a écrit :
 On Thursday, March 01, 2012 17:08:44 bearophile wrote:
 I think std.typecons.Tuples merit to be a little more citizens in D and
 Phobos. I think reducing the usage of "out" argument, and replacing them
 with a tuple result, will avoid mistakes and make the code look better. In
 std.math there are functions that maybe are better to use
 std.typecons.Tuple:

 pure nothrow  trusted real frexp(real value, out int exp);
 ==>
 pure nothrow  trusted Tuple!(real, int) frexp(real value);

Having good tuple support is great, but out parameters are great too. I'm sure that there are plenty of cases where using out parameters is actually far cleaner than using tuples, since you don't have multiple return values to deal with. So, better enabling tuples is great, but I don't think that we're necessarily moving in a good direction if we're trying to eliminate out parameters in favor of tuples. - Jonathan M Davis

I don't think out parameter is a great idea. This is rather confusing. I tend to think as function's parameter as input of the function and return value as an output. Books usualy agree, so I guess it is a valid point. OOP give us another parameter to play with : this. It act as a state that can be modified. UFCS is nice to extends that outside OOP. This is a way better alternative than out parameter. Tuples are nice too. Since we have auto, this isn't a big deal. Both should be preferred to out parameters IMO, because the later cannot be differentiated at the calling point and force the programer to refers to the function declaration all the time or use its - limited and sometime inaccurate - memory. This is something we want to avoid.
Mar 01 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 02/03/2012 00:09, Jonathan M Davis a écrit :
 When you're looking to mutate existing variables in the caller, using out
 parameters results in cleaner code.

I'd argue that not mutating parameter result in cleaner code most of the time.
 Tuples are inherently messier, because you
 have to deal with multiple return values. They also often do poorly when you
 need to use the functional programming, because often you want all of the
 return values for later use but only want to pass _one_ of them to the
 function that you're passing the result to.

The first time I encountered tuple was using Caml. This claim doesn't support my practical experience.
Mar 02 2012
prev sibling parent bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 I'm sure 
 that there are plenty of cases where using out parameters is actually far 
 cleaner than using tuples, since you don't have multiple return values to deal 
 with.

Are you able to show me one or more examples where using one or more out arguments is in your opinion more clear (and safer!) than using a tuple with the proposed unpacking syntax? With a tuple you have to deal with multiple return values, but the semantics is cleaner (function => resulting tuple). The number of variables doesn't change, because with "out" you need to define them any way, before the call. If you think with tuples you lose the names of the out arguments there is a way to avoid this problem (but I don't know if in some situations out arguments are more efficient than tuples): pure nothrow trusted real frexp(real value, out int exp); ==> pure nothrow trusted Tuple!(real, int,"exp") frexp(real value); Also, with a tuple result there is no risk of confusion if an argument is "out" (do you remember the discussion where people have asked a ref/out annotation at the calling point too, as in C#? With tuples this problem doesn't exists). Thank you for your answer, bye, bearophile
Mar 01 2012
prev sibling next sibling parent kennytm <kennytm gmail.com> writes:
bearophile <bearophileHUGS lycos.com> wrote:
(snip) 
 It's good for tuples to become more common in D code. Some time ago I
 have asked the built-in associative arrays to grow a method to iterate on
 key-value pairs, named "byPair":
 http://d.puremagic.com/issues/show_bug.cgi?id=5466
 

 Bye,
 bearophile

Associative arrays (should) have UFCS, so one could just define property auto byPair(AA)(AA aa) { return zip(aa.byKey, aa.byValue); } in std.array. Or object.di could just define a Pair!(K,V) structure which a Tuple!(K,V) has an opAssign defined for it.
Mar 01 2012
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, March 02, 2012 00:05:22 deadalnix wrote:
 Le 01/03/2012 23:33, Jonathan M Davis a écrit :
 On Thursday, March 01, 2012 17:08:44 bearophile wrote:
 I think std.typecons.Tuples merit to be a little more citizens in D and
 Phobos. I think reducing the usage of "out" argument, and replacing them
 with a tuple result, will avoid mistakes and make the code look better.
 In
 std.math there are functions that maybe are better to use
 std.typecons.Tuple:
 
 pure nothrow  trusted real frexp(real value, out int exp);
 ==>
 pure nothrow  trusted Tuple!(real, int) frexp(real value);

[snip] Having good tuple support is great, but out parameters are great too. I'm sure that there are plenty of cases where using out parameters is actually far cleaner than using tuples, since you don't have multiple return values to deal with. So, better enabling tuples is great, but I don't think that we're necessarily moving in a good direction if we're trying to eliminate out parameters in favor of tuples. - Jonathan M Davis

I don't think out parameter is a great idea. This is rather confusing. I tend to think as function's parameter as input of the function and return value as an output. Books usualy agree, so I guess it is a valid point. OOP give us another parameter to play with : this. It act as a state that can be modified. UFCS is nice to extends that outside OOP. This is a way better alternative than out parameter. Tuples are nice too. Since we have auto, this isn't a big deal. Both should be preferred to out parameters IMO, because the later cannot be differentiated at the calling point and force the programer to refers to the function declaration all the time or use its - limited and sometime inaccurate - memory. This is something we want to avoid.

When you're looking to mutate existing variables in the caller, using out parameters results in cleaner code. Tuples are inherently messier, because you have to deal with multiple return values. They also often do poorly when you need to use the functional programming, because often you want all of the return values for later use but only want to pass _one_ of them to the function that you're passing the result to. At other times, tuples are nicer - like when you actually _want_ the return value packed together (though often, if that's what you really want, a struct might be better). And if you're not looking to assign the parts of a tuple to existing variables, then they're not quite as bad as when you are, since you don't necessarily have to then assign the pieces to other variables. Both have value, though if you need a lot of either, you should probably consider whether a struct or class would suit what you're doing better. - Jonathan M Davis
Mar 01 2012
next sibling parent kennytm <kennytm gmail.com> writes:
"Jonathan M Davis" <jmdavisProg gmx.com> wrote:

 When you're looking to mutate existing variables in the caller, using out 
 parameters results in cleaner code. Tuples are inherently messier, because you 
 have to deal with multiple return values. They also often do poorly when you 
 need to use the functional programming, because often you want all of the 
 return values for later use but only want to pass _one_ of them to the 
 function that you're passing the result to.
 

You use 'ref' to mutate existing variable, not 'out'. Multiple return value doesn't replace the use case of ref. Not sure how messy it is to extract one value out of the tuple. It is much more messier to ignore an 'out' parameter than a field in a tuple in the call site.
 At other times, tuples are nicer - like when you actually _want_ the return 
 value packed together (though often, if that's what you really want, a struct 
 might be better). And if you're not looking to assign the parts of a tuple to 
 existing variables, then they're not quite as bad as when you are, since you 
 don't necessarily have to then assign the pieces to other variables.
 
 Both have value, though if you need a lot of either, you should probably 
 consider whether a struct or class would suit what you're doing better.
 
 - Jonathan M Davis

Mar 01 2012
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 When you're looking to mutate existing variables in the caller, using out 
 parameters results in cleaner code. Tuples are inherently messier, because you 
 have to deal with multiple return values.

out arguments have two risks: - If you assign a value to a variable and then use it to call a function, the precedent value is ignored and overwritten. - If in a function you forget to assign an out argument, the D compiler produces no errors. Both source of bugs are not present with tuples.
 They also often do poorly when you 
 need to use the functional programming,

As you know functional languages use tuples all the time.
 At other times, tuples are nicer - like when you actually _want_ the return 
 value packed together (though often, if that's what you really want, a struct 
 might be better).

Defining a struct makes your code messier. D tuples support named fields too, so the advantage of using a struct is limited.
 And if you're not looking to assign the parts of a tuple to 
 existing variables, then they're not quite as bad as when you are, since you 
 don't necessarily have to then assign the pieces to other variables.

There are parts of your post that I don't fully understand.
 Both have value, though if you need a lot of either, you should probably 
 consider whether a struct or class would suit what you're doing better.

For most usages of a tuple a class instance means useless heap activity and more work for the GC. Using a struct to return the results of a function is sometimes acceptable, but most times I don't use tuples for that purpose. Consider this code in my original post, defining two static structs doesn't do much good to such kind of code: Tuple!(char,int)[] pairs2 = frequences.pairs; schwartzSort!(c_f => tuple(-c_f[1], c_f[0]))(pairs2); foreach (c_f; pairs2) writeln(c_f[1], " ", c_f[0]); Tuples are often defined and used on the fly, in-place. Bye, bearophile
Mar 01 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 02/03/2012 02:10, Jonathan M Davis a écrit :
 On Thursday, March 01, 2012 18:57:15 bearophile wrote:
 Jonathan M Davis:
 They also often do poorly when you
 need to use the functional programming,

As you know functional languages use tuples all the time.

Yes, but chaining functions is the issue. It doesn't work well with tuples unless the function you're passing the result to wants the tuple. If all it wants is one piece of the tuple, then that doesn't work well at all. You're forced to assign the tuple to something else and then call then function rather than chain calls. That's one of the reasons that you constantly end up using stuff like let expressions and pattern matching in functional languages. You don't _want_ a tuple. Dealing with a tuple is annoying. It's just that it's often the best tool that you have to pass disparate stuff around, so that's what you use.
 And if you're not looking to assign the parts of a tuple to
 existing variables, then they're not quite as bad as when you are, since
 you don't necessarily have to then assign the pieces to other variables.


It is often really annoying to have to deal with tuple return values, because you have to worry about unpacking the result. I don't want to use a tuple in the caller. Tuples are generally for grouping unrelated data that you don't necessarily want to keep togother (since if you did, you'd generally use a struct). I want the result to actually be assigned to variables. That is definitely cleaner with out than with tuples. int exp; auto result = frexp(value, exp); vs auto tup = frexp(value); result = tup[0]; exp = tup[1]; Getting tuple return values is annoying. Yes, it can be useful, but most stuff doesn't operate on tuples. It operates on the pieces of tuples. So, you have to constantly break them up. So, using out results in much nicer code. It always feels like I'm fighting the code when I have to deal with tuple return values. - Jonathan M Davis

Do you rally think that shorter is always better ? I don't think so. I think better is what the piece of code do pretty much what you expect it to do. And most of the time, I want to pass argument to a function not return value throw arguments. At the callee place, version look just like any other function call, but exp isn't a parameter, it is actually a returned value. You cannot know that reading the code, you need to know about frexp declaration - thus you scroll more source code/documentation or rely on your memory that will sometime fail. To emphasis that, just imagine the same scenario with a function that isn't from the standard lib. This practice will confuse a newcomer that don't know the stdlib well, and, used in thrid party code, will confuse anyone expect the one that write that piece of code.
Mar 02 2012
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, March 01, 2012 18:57:15 bearophile wrote:
 Jonathan M Davis:
 They also often do poorly when you
 need to use the functional programming,

As you know functional languages use tuples all the time.

Yes, but chaining functions is the issue. It doesn't work well with tuples unless the function you're passing the result to wants the tuple. If all it wants is one piece of the tuple, then that doesn't work well at all. You're forced to assign the tuple to something else and then call then function rather than chain calls. That's one of the reasons that you constantly end up using stuff like let expressions and pattern matching in functional languages. You don't _want_ a tuple. Dealing with a tuple is annoying. It's just that it's often the best tool that you have to pass disparate stuff around, so that's what you use.
 And if you're not looking to assign the parts of a tuple to
 existing variables, then they're not quite as bad as when you are, since
 you don't necessarily have to then assign the pieces to other variables.


It is often really annoying to have to deal with tuple return values, because you have to worry about unpacking the result. I don't want to use a tuple in the caller. Tuples are generally for grouping unrelated data that you don't necessarily want to keep togother (since if you did, you'd generally use a struct). I want the result to actually be assigned to variables. That is definitely cleaner with out than with tuples. int exp; auto result = frexp(value, exp); vs auto tup = frexp(value); result = tup[0]; exp = tup[1]; Getting tuple return values is annoying. Yes, it can be useful, but most stuff doesn't operate on tuples. It operates on the pieces of tuples. So, you have to constantly break them up. So, using out results in much nicer code. It always feels like I'm fighting the code when I have to deal with tuple return values. - Jonathan M Davis
Mar 01 2012
parent reply bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 Yes, but chaining functions is the issue. It doesn't work well with tuples 
 unless the function you're passing the result to wants the tuple. If all it 
 wants is one piece of the tuple, then that doesn't work well at all. You're 
 forced to assign the tuple to something else and then call then function 
 rather than chain calls.

In the years I have used a mountain of tuples in Python, but I barely perceive that problem, so I think it's not so bad.
 int exp;
 auto result = frexp(value, exp);
 
 vs
 
 auto tup = frexp(value);
 result = tup[0];
 exp = tup[1];

I have assumed to use a sane tuple unpacking syntax. So the second part of your comparison is: immutable (result, exp) = frexp(value); That is better than your version with the out argument, safer, looks better, and you are even able to make both results constant.
 Getting tuple return values is annoying. Yes, it can be useful, but most stuff 
 doesn't operate on tuples. It operates on the pieces of tuples. So, you have 
 to constantly break them up. So, using out results in much nicer code.

I think that the tuple unpacking syntax is able to avoid part of your problems. The point of my original post, that maybe was lost in the bulk of the text, was that an unpacking syntax sugar is very useful if you want to use tuples for real in D. Bye and thank you, bearophile
Mar 01 2012
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 02.03.2012 6:06, bearophile wrote:
 Jonathan M Davis:

 Yes, but chaining functions is the issue. It doesn't work well with tuples
 unless the function you're passing the result to wants the tuple. If all it
 wants is one piece of the tuple, then that doesn't work well at all.


just stick in .expand ? void f(int x, int y){ } void main() { Tuple!(int, int) a; f(a.expand); } BTW it's nowhere to be found here http://dlang.org/phobos/std_typecons.html You're
 forced to assign the tuple to something else and then call then function
 rather than chain calls.

In the years I have used a mountain of tuples in Python, but I barely perceive that problem, so I think it's not so bad.
 int exp;
 auto result = frexp(value, exp);

 vs

 auto tup = frexp(value);
 result = tup[0];
 exp = tup[1];

I have assumed to use a sane tuple unpacking syntax. So the second part of your comparison is: immutable (result, exp) = frexp(value); That is better than your version with the out argument, safer, looks better, and you are even able to make both results constant.

+1
 Getting tuple return values is annoying. Yes, it can be useful, but most stuff
 doesn't operate on tuples. It operates on the pieces of tuples. So, you have
 to constantly break them up. So, using out results in much nicer code.

I think that the tuple unpacking syntax is able to avoid part of your problems.

s/part/most I'd say that results clearly belond to the left side of x = fun(...) expression, and tuples + unpack syntax are the way to make it consistent. With all sugar going on around Tuples, e.g. .tupleof, unpacking, I can't help but wonder why are they not built-ins. At least they should go to object.d/druntime like AA do. -- Dmitry Olshansky
Mar 01 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, March 02, 2012 11:41:16 Dmitry Olshansky wrote:
 On 02.03.2012 6:06, bearophile wrote:
 Jonathan M Davis:
 Yes, but chaining functions is the issue. It doesn't work well with
 tuples
 unless the function you're passing the result to wants the tuple. If all
 it
 wants is one piece of the tuple, then that doesn't work well at all.


just stick in .expand ? void f(int x, int y){ } void main() { Tuple!(int, int) a; f(a.expand); }

That's assuming that you're passing all of the pieces of the tuple to the function. Often, that's not the case at all. Take the findSplit trio, for instance. What are the odds that you're going to want to pass all of the elements in the tuples that any of the return to another function? About zero, I'd say. It's _much_ more likely that you're going to want to take the results and then pass _one_ of them to another function. So, as it stands, chaining with those functions just doesn't work unless you only care about one of the results in the tuple. - Jonathan M Davis
Mar 02 2012
next sibling parent kennytm <kennytm gmail.com> writes:
Jonathan M Davis <jmdavisProg gmx.com> wrote:

 That's assuming that you're passing all of the pieces of the tuple to the 
 function. Often, that's not the case at all. Take the findSplit trio, for 
 instance. What are the odds that you're going to want to pass all of the 
 elements in the tuples that any of the return to another function? About zero, 
 I'd say. It's _much_ more likely that you're going to want to take the results 
 and then pass _one_ of them to another function. So, as it stands, chaining 
 with those functions just doesn't work unless you only care about one of the 
 results in the tuple.
 
 - Jonathan M Davis

How does 'out' make chaining any easier? Suppose we have a `R3 findSplit2(R1, R2, out R4, out R5)`, how to chain if we want to pass the R4 to another function? R5 ignored; R4 theRange; findSplit2(haystack, needle, theRange, ignored); return doSomething(theRange); vs return doSomething(findSplit(haystack, needle)[1]);
Mar 02 2012
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, March 02, 2012 09:31:14 kennytm wrote:
 Jonathan M Davis <jmdavisProg gmx.com> wrote:
 That's assuming that you're passing all of the pieces of the tuple to the
 function. Often, that's not the case at all. Take the findSplit trio, for
 instance. What are the odds that you're going to want to pass all of the
 elements in the tuples that any of the return to another function? About
 zero, I'd say. It's _much_ more likely that you're going to want to take
 the results and then pass _one_ of them to another function. So, as it
 stands, chaining with those functions just doesn't work unless you only
 care about one of the results in the tuple.
 
 - Jonathan M Davis

How does 'out' make chaining any easier? Suppose we have a `R3 findSplit2(R1, R2, out R4, out R5)`, how to chain if we want to pass the R4 to another function? R5 ignored; R4 theRange; findSplit2(haystack, needle, theRange, ignored); return doSomething(theRange); vs return doSomething(findSplit(haystack, needle)[1]);

True, you can't chain using the out parameters, but you _can_ chain using the return value, whereas if you have a tuple, you can't chain _at all_ unless you actually need all of the returned values (either as a tuple or expanded) or if you only need _one_ of the returned values, in which case you can use the subscript operator. So, you can definitely chain better without a tuple than with. - Jonathan M Davis
Mar 02 2012
next sibling parent reply kennytm <kennytm gmail.com> writes:
Jonathan M Davis <jmdavisProg gmx.com> wrote:
 On Friday, March 02, 2012 09:31:14 kennytm wrote:
 Jonathan M Davis <jmdavisProg gmx.com> wrote:
 That's assuming that you're passing all of the pieces of the tuple to the
 function. Often, that's not the case at all. Take the findSplit trio, for
 instance. What are the odds that you're going to want to pass all of the
 elements in the tuples that any of the return to another function? About
 zero, I'd say. It's _much_ more likely that you're going to want to take
 the results and then pass _one_ of them to another function. So, as it
 stands, chaining with those functions just doesn't work unless you only
 care about one of the results in the tuple.
 
 - Jonathan M Davis

How does 'out' make chaining any easier? Suppose we have a `R3 findSplit2(R1, R2, out R4, out R5)`, how to chain if we want to pass the R4 to another function? R5 ignored; R4 theRange; findSplit2(haystack, needle, theRange, ignored); return doSomething(theRange); vs return doSomething(findSplit(haystack, needle)[1]);

True, you can't chain using the out parameters, but you _can_ chain using the return value, whereas if you have a tuple, you can't chain _at all_ unless you actually need all of the returned values (either as a tuple or expanded) or if you only need _one_ of the returned values, in which case you can use the subscript operator. So, you can definitely chain better without a tuple than with. - Jonathan M Davis

You can just chain with return doSomething(findSplit(haystack, needle)[0]); if you just need the return value. Compare with 'out': R4 ignored; R5 ignored2; return doSomething(findSplit(haystack, needle, ignored, ignored2)); How do you chain with _partial_ amount of return values with 'out'?
Mar 02 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 02/03/2012 11:10, Jonathan M Davis a écrit :
 On Friday, March 02, 2012 09:53:19 kennytm wrote:
 You can just chain with

      return doSomething(findSplit(haystack, needle)[0]);

 if you just need the return value. Compare with 'out':

      R4 ignored;
      R5 ignored2;
      return doSomething(findSplit(haystack, needle, ignored, ignored2));

 How do you chain with _partial_ amount of return values with 'out'?

If the function uses out, then you can chain the return value without losing the values which were assigned to the out arguments, but if you have a tuple, and you select one of the elements in the tuple to chain, you lose the others. The only way to get _all_ of the values in the tuple is to assign the tuple to a variable, in which case, you can't chain at all. - Jonathan M Davis

But you are assigning to a variable, you just declare it before. Additionally, you loose all possibility to use auto.
Mar 02 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, March 02, 2012 09:53:19 kennytm wrote:
 You can just chain with
 
     return doSomething(findSplit(haystack, needle)[0]);
 
 if you just need the return value. Compare with 'out':
 
     R4 ignored;
     R5 ignored2;
     return doSomething(findSplit(haystack, needle, ignored, ignored2));
 
 How do you chain with _partial_ amount of return values with 'out'?

If the function uses out, then you can chain the return value without losing the values which were assigned to the out arguments, but if you have a tuple, and you select one of the elements in the tuple to chain, you lose the others. The only way to get _all_ of the values in the tuple is to assign the tuple to a variable, in which case, you can't chain at all. - Jonathan M Davis
Mar 02 2012
next sibling parent kennytm <kennytm gmail.com> writes:
Jonathan M Davis <jmdavisProg gmx.com> wrote:
 On Friday, March 02, 2012 09:53:19 kennytm wrote:
 You can just chain with
 
     return doSomething(findSplit(haystack, needle)[0]);
 
 if you just need the return value. Compare with 'out':
 
     R4 ignored;
     R5 ignored2;
     return doSomething(findSplit(haystack, needle, ignored, ignored2));
 
 How do you chain with _partial_ amount of return values with 'out'?

If the function uses out, then you can chain the return value without losing the values which were assigned to the out arguments, but if you have a tuple, and you select one of the elements in the tuple to chain, you lose the others. The only way to get _all_ of the values in the tuple is to assign the tuple to a variable, in which case, you can't chain at all. - Jonathan M Davis

I see what you mean. However, this is useful only when you know one of the return value is special, and make the rest 'out' parameters, e.g. File openFile(string fn, string mode, out ErrorCode errCode); because the API designer know people seldom focus on the 'errCode'. But if not all return values are considered unimportant (such as findSplit and remquo), randomly making some of them as the 'out' parameter just make the normal use cases more messy. Besides, you can't use type inference with 'out' parameters. You don't actually know the types R4 and R5 in my findSplit2 example.
Mar 02 2012
prev sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, March 02, 2012 12:59:43 kennytm wrote:
 Jonathan M Davis <jmdavisProg gmx.com> wrote:
 On Friday, March 02, 2012 09:53:19 kennytm wrote:
 You can just chain with
 
 return doSomething(findSplit(haystack, needle)[0]);
 
 if you just need the return value. Compare with 'out':
 R4 ignored;
 R5 ignored2;
 return doSomething(findSplit(haystack, needle, ignored, ignored2));
 
 How do you chain with _partial_ amount of return values with 'out'?

If the function uses out, then you can chain the return value without losing the values which were assigned to the out arguments, but if you have a tuple, and you select one of the elements in the tuple to chain, you lose the others. The only way to get _all_ of the values in the tuple is to assign the tuple to a variable, in which case, you can't chain at all. - Jonathan M Davis

I see what you mean. However, this is useful only when you know one of the return value is special, and make the rest 'out' parameters, e.g. File openFile(string fn, string mode, out ErrorCode errCode); because the API designer know people seldom focus on the 'errCode'. But if not all return values are considered unimportant (such as findSplit and remquo), randomly making some of them as the 'out' parameter just make the normal use cases more messy. Besides, you can't use type inference with 'out' parameters. You don't actually know the types R4 and R5 in my findSplit2 example.

It's not like using out is fantastic and tuple sucks. They both have pros and cons. My point is that it's not the case that always returning tuples is better, which seems to be the point that Bearophile is trying to push. There _are_ downsides to returning tuples. Whether a tuple or an out parameter is better depends on the function and the context in which it is used. - Jonathan M Davis
Mar 02 2012
next sibling parent kennytm <kennytm gmail.com> writes:
"Jonathan M Davis" <jmdavisProg gmx.com> wrote:

 It's not like using out is fantastic and tuple sucks. They both have pros and 
 cons. My point is that it's not the case that always returning tuples is 
 better, which seems to be the point that Bearophile is trying to push. There 
 _are_ downsides to returning tuples. Whether a tuple or an out parameter is 
 better depends on the function and the context in which it is used.
 
 - Jonathan M Davis

bearophile's example (frexp, remquo) are good examples where tuple return is better than 'out' parameters though. Another Phobos function, I think, which should use 'out' instead of Tuples is: - std.file.getTimes (have the two 'out' parameters, and the function itself returns 'void'!) While some functions should remain using 'out': - std.stream.InputStream.read (due to overloading) And of course the extern(C) functions need to use 'out' instead of Tuple return :)
Mar 02 2012
prev sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Has anyone seen my recent thread about using the if+auto feature and
opCast(bool)? It's not related to tuples but I thought it was a cool D
feature. I admit I named the thread a pretty stupid name, but it was 6
AM when I posted it :)
(http://forum.dlang.org/thread/mailman.195.1330399006.24984.digitalmars-d puremagic.com)
Mar 02 2012
prev sibling parent "renoX" <renozyx gmail.com> writes:
On Friday, 2 March 2012 at 10:13:17 UTC, Jonathan M Davis wrote:
 If the function uses out, then you can chain the return value 
 without losing
 the values which were assigned to the out arguments, but if you 
 have a tuple,
 and you select one of the elements in the tuple to chain, you 
 lose the others.
 The only way to get _all_ of the values in the tuple is to 
 assign the tuple to
 a variable, in which case, you can't chain at all.

 - Jonathan M Davis

Sure, but due to D's syntax which doesn't distinguish in/out/ref params in a function call, this is quite confusing to read: when you read f(x,y) which is in, which is out? If D's syntax was f(x, y) ( to distinguish out or ref parameter), this would be easy to read, but this isn't the case. At least with tuples you don't have this issue. Not that I consider tuples always the good answer: for the common use case where you want to return an error code and a result, the Maybe "monad" is better.. BR, renoX
Mar 02 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 02/03/2012 03:06, bearophile a écrit :
 Jonathan M Davis:

 Yes, but chaining functions is the issue. It doesn't work well with tuples
 unless the function you're passing the result to wants the tuple. If all it
 wants is one piece of the tuple, then that doesn't work well at all. You're
 forced to assign the tuple to something else and then call then function
 rather than chain calls.

In the years I have used a mountain of tuples in Python, but I barely perceive that problem, so I think it's not so bad.
 int exp;
 auto result = frexp(value, exp);

 vs

 auto tup = frexp(value);
 result = tup[0];
 exp = tup[1];

I have assumed to use a sane tuple unpacking syntax. So the second part of your comparison is: immutable (result, exp) = frexp(value);

You got it right. +1
Mar 02 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, March 02, 2012 10:27:02 deadalnix wrote:
 Le 02/03/2012 00:09, Jonathan M Davis a =C3=A9crit :
 When you're looking to mutate existing variables in the caller, usi=


 parameters results in cleaner code.

I'd argue that not mutating parameter result in cleaner code most of =

 time.
=20
 Tuples are inherently messier, because you
 have to deal with multiple return values. They also often do poorly=


 you need to use the functional programming, because often you want =


 the return values for later use but only want to pass _one_ of them=


 the function that you're passing the result to.

The first time I encountered tuple was using Caml. This claim doesn't=

 support my practical experience.

Yes. Functional languages use tuples. But I'm talking about chaining fu= nctions=20 like you would in a functional language. But functional languages use p= attern=20 matching and other stuff to make it not as big a problem as it is in D,= and=20 often in functional languages, you _still_ have to do their equivalent = of=20 assigning the pieces of the tuple to variables. Tuples are a bane of fu= nction=20 chaining. - Jonathan M Davis
Mar 02 2012
prev sibling parent Don <nospam nospam.com> writes:
On 01.03.2012 23:08, bearophile wrote:
 I think std.typecons.Tuples merit to be a little more citizens in D and Phobos.
 I think reducing the usage of "out" argument, and replacing them with a tuple
result, will avoid mistakes and make the code look better. In std.math there
are functions that maybe are better to use std.typecons.Tuple:

 pure nothrow  trusted real frexp(real value, out int exp);
 ==>
 pure nothrow  trusted Tuple!(real, int) frexp(real value);


 nothrow  trusted real remquo(real x, real y, out int n);
 ==>
 nothrow  trusted Tuple!(real, int) remquo(real x, real y);

They're defined that way because they come from C, and they're in IEEE754.
Mar 02 2012