Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# Design Notes for Feb 11, 2015 #1207

Closed
MadsTorgersen opened this issue Mar 12, 2015 · 44 comments
Closed

C# Design Notes for Feb 11, 2015 #1207

MadsTorgersen opened this issue Mar 12, 2015 · 44 comments

Comments

@MadsTorgersen
Copy link
Contributor

C# Design Meeting Notes for Feb 11, 2015

The design notes can be found at https://github.com/dotnet/roslyn/blob/master/docs/designNotes/2015-02-11%20C%23%20Design%20Meeting.md

Discussion on these design notes are in this issue.

@MadsTorgersen MadsTorgersen self-assigned this Mar 12, 2015
@MadsTorgersen MadsTorgersen added this to the C# 7 and VB 15 milestone Mar 12, 2015
@MadsTorgersen MadsTorgersen changed the title C# Design Meeting Notes for Feb 11, 2015 C# Design Notes for Feb 11, 2015 Mar 12, 2015
@HaloFour
Copy link

I wonder where this might leave other proposals such as #181, an RAII-like syntax for IDisposable:

public void CopyToFile(Stream input, string fileName)
{
    using FileStream output = new FileStream(fileName, FileMode.OpenOrCreate);
    input.CopyTo(output);
} // output.Dispose() automatically called when output goes out of scope

@MgSam
Copy link

MgSam commented Mar 12, 2015

Agree with the decision to drop destructible types. As I said in that thread, I'd like to see a much more natural solution to the problem of deterministic memory (and other resource) cleanup. Doing that seems like it would almost by necessity require CLR changes.

I don't think the lack of Intellisense for declaring the deconstruction of a tuple-returning method is a problem- it seems highly likely to me that people will use var to infer the return type as they do right now. There could be an analyzer lightbulb which offers to turn your single var into the correct deconstruction.

Supporting out parameters more naturally would definitely be an improvement. The framework methods that use out parameters are not going away and are an annoying pain point currently.

Type equivalence- I think CLR changes are the best solution here. You guys have thrown around the idea for a long time of having better support for structural equality- it seems like building it into the CLR is the most general solution that would reach the widest user base (as then any CLR language could take advantage). Structural equality has been a huge success in TypeScript, I don't think we should shortchange C# some of the same benefits just to maintain downlevel compilation.

@danfma
Copy link

danfma commented Mar 12, 2015

@MadsTorgersen, I did not agree with the current design of tuples in the sense that it could only be used on the same assembly of its definition. If you work with other languages, like kotlin, scala, groovy etc, all these languages have a tuple type well defined, based on some generic type often, that can be used across any library (in our case, assembly).

I understood your points, but if the tupple could not be used across assemblies, we could end up with the same problem of passing an anonymous type object throught a controller, in MVC, that will not be able to be used on the view, even if the model type is dynamic.

I liked a lot about the definition, except that I don't think that tuples must be named. The definition of tuples just define an ordered sequence of values, not necessarily named. If you get implementations like that in kotlin or scala, you will see that it defines the tuple as just (int, string), for example. So to play with it we could just write:

(int, string) x = (10, "hello");
// or
Tuple<int, string> x = ...;

Then to deconstruct:

var (number, someString) = x; // using type inference

These types does not define a name for a member but I can get each value by using a property like: .Item1, .Item2... etc. So I could take the x.Item1 if needed. Another point is that each tuple can be enumerated like you said, so could use:

foreach (object value in tupleX) {
}

Anyway, that is just my point of view.

@ashmind
Copy link
Contributor

ashmind commented Mar 12, 2015

I think type equivalence in CLR is long overdue. It can be useful for tuples, delegates, type redirects and multiple other cases (e.g. ASP.NET's [AssemblyNeutral]). In fact, some type equivalence already exists, in form of [TypeIdentifier] -- but it seems to be very limited to COM scenarios.

It doesn't have to be some ad-hoc auto-discovered equivalence though -- a solution based on explicit identifiers could work just as well, with compiler generating corresponding attributes.

I liked a lot about the definition, except that I don't think that tuples must be named. The definition of tuples just define an ordered sequence of values, not necessarily named.

I think unnamed tuples is just not a good fit for C#. They aren't good for Intellisense or API discovery (aside from obvious cases such as TryParse), and tuples with more than a few values would quickly turn into a mess:

# Actual Perl API:
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
       $atime,$mtime,$ctime,$blksize,$blocks)
           = stat($filename);

Btw it seems that Kotlin have dropped nameless tuples: http://blog.jetbrains.com/kotlin/migrating-tuples/.

@paulomorgado
Copy link

Tuples

I think you're overthinking it and trying to overdo it.

Tuples should be just a grouping of nameless values. If you need to name them, deconstruct it.

At first look, it might look like you need the names to understand the return value of this:

public (int sum, int count) Tally(IEnumerable<int> values) 
{
    var sum = 0; var count = 0;
    foreach (var value in values) { sum += value; count++; }
    return (sum, count); // target typed to the return type
}

But a good API should already tell you what it does and what a method is returning. We have lived with that for simple return values and should be able to live with it with tuple return values.

public (int, int) Tally(IEnumerable<int> values) 
{
    var sum = 0; var count = 0;
    foreach (var value in values) { sum += value; count++; }
    return (sum, count); // target typed to the return type
}

No matter how strange it might look.

However, local names for tuple parts might be usefull:

public (int, int) Tally(IEnumerable<int> values) 
{
    var res = (sum: 0, count: 0);
    foreach (var value in values) { res.sum += value; res.count++; }
    return res;
}

Having a tuple variable can be useful because you might be working on values returned from other methods:

public (int, int) Tally(IEnumerable<int> values) 
{
    (int sum, int count) res = GetSomething();
    foreach (var value in values) { res.sum += value; res.count++; }
    return res;
}

Although deconstructing it would work just fine:

public (int, int) Tally(IEnumerable<int> values) 
{
    (int sum, int count) = GetSomething();
    foreach (var value in values) { sum += value; count++; }
    return (sum, count);
}

If local names are not given, the tuple members should always be possible through properties like Item1, Item2, ...

This design keeps things simple and:

Tuples should be thought of as temporary, ephemeral constellations of values without inherent conceptual connection: they just "happen to be traveling together".

Don't try to make tuples the new anonymous types!

Type equivalence

Keeping things simple allows to use the already exiting Tuple types and type equivalence is solved.

Conversions

Other than reordering, all the proposed convertions should be possible. Because names are only local and ephemeral, renaming is not an issue.

But I would also like to have these:

/* Rest */
(int age, string firstName, string lastName) t1 = (29, "Johnny", "Tuple");
(int age, var names) t2 = t1; // (int age, (string firstName, string lastName))

(int first, int second, int third) t3 = new int[] { 1, 2, 3 };
(int first, int[] rest) t4 = new int[] { 1, 2, 3 };
(int[] rest, int last) t5 = new int[] { 1, 2, 3 };
(int first, int[] middle, int last) t6 = new int[] { 1, 2, 3, 4 };

(All the above rules should apply to either tuples, arrays and instances of the already existing Tuple types.

dynamic really poses an hard to solve problem. Or not. If names are not part of the tuple value, it really doesn't make sense using them.

Out parameters

We are not here to be F#--!

Although p/Invoke already allows out parameters to be converted to returning values and return HRESULTs converted into exceptions thrown, I don't think this is a good move to C#.

If the proposed conversion is allowed, people will try to do this:

Task<(bool, int)> t = Task.Run(TryGet);

and not understand why they can't.

With declaration expressions, almost the same can be achieved:

(bool success, int value) tuple = TryGet(out int value), value));
Task<(bool, int)> task = Task.Run(() => (TryGet(out int value), value)));

@Przemyslaw-W
Copy link

I think names are essential. For tuples like (int, int, int) relying only
on position when deconstructing just begs for bugs.

I also like the original syntactic sugar around out parameters. I never
liked them and found them as a smell from the old days. I personally
tolerate out only in interop code and when using Try* pattern. Using tuples
to hide out parameters will allow us to treat methods using them as methods
with multiple return values - which they conceptually are. All this with
clean and consistent syntax. What is more, code like this:

(var success, var parsed) = int.TryParse(stringValue);

can be compiled into:

int parsed;

bool success = int.TryParse(stringValue, out parsed);

without the need to allocate temporary tuple. I don't know if this
would eliminate the need for declaration expressions entirely, but it
would certainly handle the 'out' use case.

2015-03-13 10:05 GMT+01:00 Paulo Morgado [email protected]:

Tuples

I think you're overthinking it and trying to overdo it.

Tuples should be just a grouping of nameless values. If you need to name
them, deconstruct it.

At first look, it might look like you need the names to understand the
return value of this:

public (int sum, int count) Tally(IEnumerable values)
{
var sum = 0; var count = 0;
foreach (var value in values) { sum += value; count++; }
return (sum, count); // target typed to the return type
}

But a good API should already tell you what it does and what a method is
returning. We have lived with that for simple return values and should be
able to live with it with tuple return values.

public (int, int) Tally(IEnumerable values)
{
var sum = 0; var count = 0;
foreach (var value in values) { sum += value; count++; }
return (sum, count); // target typed to the return type
}

No matter how strange it might look.

However, local names for tuple parts might be usefull:

public (int, int) Tally(IEnumerable values)
{
var res = (sum: 0, count: 0);
foreach (var value in values) { res.sum += value; res.count++; }
return res;
}

Having a tuple variable can be useful because you might be working on
values returned from other methods:

public (int, int) Tally(IEnumerable values)
{
(int sum, int count) res = GetSomething();
foreach (var value in values) { res.sum += value; res.count++; }
return res;
}

Although deconstructing it would work just fine:

public (int, int) Tally(IEnumerable values)
{
(int sum, int count) = GetSomething();
foreach (var value in values) { sum += value; count++; }
return (sum, count);
}

If local names are not given, the tuple members should always be possible
through properties like Item1, Item2, ...

This design keeps things simple and:

Tuples should be thought of as temporary, ephemeral constellations of
values without inherent conceptual connection: they just "happen to be
traveling together".

Don't try to make tuples the new anonymous types!
Type equivalence

Keeping things simple allows to use the already exiting Tuple types and
type equivalence is solved.
Conversions

Other than reordering, all the proposed convertions should be possible.
Because names are only local and ephemeral, renaming is not an issue.

But I would also like to have these:

/* Rest */
(int age, string firstName, string lastName) t1 = (29, "Johnny", "Tuple");
(int age, var names) t2 = t1; // (int age, (string firstName, string lastName))

(int first, int second, int third) t3 = new int[] { 1, 2, 3 };
(int first, int[] rest) t4 = new int[] { 1, 2, 3 };
(int[] rest, int last) t5 = new int[] { 1, 2, 3 };
(int first, int[] middle, int last) t6 = new int[] { 1, 2, 3, 4 };

(All the above rules should apply to either tuples, arrays and instances
of the already existing Tuple types.

dynamic really poses an hard to solve problem. Or not. If names are not
part of the tuple value, it really doesn't make sense using them.
Out parameters

We are not here to be F#--!

Although p/Invoke already allows out parameters to be converted to
returning values and return HRESULTs converted into exceptions thrown, I
don't think this is a good move to C#.

If the proposed conversion is allowed, people will try to do this:

Task<(bool, int)> t = Task.Run(TryGet);

and not understand why they can't.

With declaration expressions, almost the same can be achieved:

(bool success, int value) tuple = TryGet(out int value), value));
Task<(bool, int)> task = Task.Run(() => (TryGet(out int value), value)));


Reply to this email directly or view it on GitHub
#1207 (comment).

@MgSam
Copy link

MgSam commented Mar 13, 2015

I agree- names are essential. Without names I'd argue against tuples being in the language. I absolutely do not want to see return signatures of tuple types without names. The likely mess of undocumented, confusing code that such a feature would cause would not be worth the benefit.

@leppie
Copy link
Contributor

leppie commented Mar 13, 2015

Building on @paulomorgado 's suggestion.

 public ARecord(int sum, int count) Tally(IEnumerable<int> values)

Inline record declaration, perhaps transparent like anonymous types.

@svick
Copy link
Contributor

svick commented Mar 13, 2015

@leppie I think that syntax is very confusing and would take a lot of getting used to.

When reading that code, you'll be thinking something like:

Ok, so it's a method called ARecord, weird name, but okay. It takes two int parameters, sum and count and … what? Oh, it's a tuple return.

@leppie
Copy link
Contributor

leppie commented Mar 13, 2015

@svick Advanced features require advanced users.

@qrli
Copy link

qrli commented Mar 16, 2015

I agree with paulomorgado. Tuple should not have member names, to be consistent with other popular languages like F#, Python, where members are by position rather than by name. In case where names are needed, you don't really need a tuple, but something like record class.

With names, e.g. (name: "joe", age: 20), it would be more of a dictionary, thus people may expect features for dictionaries, e.g.

  • foreach (KeyValuePair kv in someTuple)
  • JSONize tuple as {name: "joe", age: 20} instead of ["joe", 20]
  • Member-order-insensitive equivalence, instead of ("joe", 20) != (20, "joe")

Also, if C# used some custom implementation instead of System.Tuple<>, languages on .NET would be speaking different languages for tuple, which destroy interoperability. Designing tuple as another version of anonymous class is not a good choice.

@MikePopoloski
Copy link

Using System.Tuple<> makes the feature unusable for many applications that care about performance. In order to freely take advantage of language-level tuple support, it needs to essentially be free from a performance perspective. I want a method returning a tuple to perform the same as if I'd passed a bunch of out parameters. Having every method call involving a tuple perform a heap allocation would be a non-starter.

@paulomorgado
Copy link

Tehicaly it ouldn't be that hard to load multiple values in the evaluation stack and have them deconstructed on the call site.

The compiler could avoid instantiating the tuple if it was never used as a whole.

But that wouldn't be CLS Compliant.

@HaloFour
Copy link

Tuples (optionally) having names isn't without precedent, and I don't think that the comparison with anonymous types is unfair. Anonymous types are tuples, and proposal #1141 even aims to implement IReadOnlyDictionary<string, object> on them.

While Tuple<> and its kin do already exist I have to agree that the implementation makes it less than ideal for a language feature, especially due to allocations.

Apple Swift allows tuples that have both named and unnamed properties, and they can be used effectively interchangeably. Those with unnamed properties are intended to be a private implementation and not to bleed into public specification. If tuples were to remain unnamed in C#, whether they are implemented on Tuple<> or not, I think the same convention should remain true. Such a return value is not suitable for public consumption, which eliminates much of its use.

@mburbea
Copy link

mburbea commented Mar 16, 2015

I'd prefer it be a struct type but I agree entirely with pualomorgado.
It seems odd to me as you normally do not declare the name of fields when you write the return parameter. e.g.

public int DogYears CalculateDogYears(int humanYears){
 ///...
}

Is a syntax error. I'd prefer it either be (int, int) or Tuple<int,int>. However, the main point should be to provide language level understanding in the form of deconstructing, local aliasing, and simplified construction. E.g. if I assign (int x,int y) pointTuple = GetPoint(). I don't mind if it actually created 3 local variables an anonymous Tuple<int, int> and then 2 ints named x and y. Similarly when I say return (x , y) I don't care if that gets translated to Tuple.Create<int, int>(x, y). That way we can reuse these objects across assembly bounds without resorting to dynamics.

@HaloFour
Copy link

If tuples are limited to being unnamed only does the assembly boundary even matter? A public API like that would be just awful and I think should be discouraged strongly, to the point of the language not permitting it.

If I saw the following signature in an assembly it would make me immediately reconsider using it:

public (int, int) CalculateHumanAge(int dogYears) { ... }

For many of the examples posted on this thread I think that names are important.

@mburbea
Copy link

mburbea commented Mar 16, 2015

Perhaps a name could appear but would not be compiled to il?
On Mar 16, 2015 12:54 PM, "HaloFour" [email protected] wrote:

If tuples are limited to being unnamed only does the assembly boundary
even matter? A public API like that would be just awful and I think should
be discouraged strongly, to the point of the language not permitting it.

If I saw the following signature in an assembly it would make me
immediately reconsider using it:

public (int, int) CalculateHumanAge(int dogYears) { ... }

For many of the examples posted on this thread I think that names are
important.


Reply to this email directly or view it on GitHub
#1207 (comment).

@HaloFour
Copy link

@mburbea

That doesn't solve the problem of the public surface of that API being awful. Encoding it as metadata in the form of attributes on the method is maybe a little better but requires that all consuming languages be supportive:

public (int years, int months) CalculateHumanAge(int dogYears) { ... }

// ->

[TupleFieldName("years"),
 TupleFieldName("months")]
public Tuple<int, int> CalculateHumanAge(int dogYears) { ... }

That still doesn't seem to solve a lot of the issues with the use-cases, and you still get the performance penalty. Honestly, tuples being limited to just this does not seem terribly useful.

@mburbea
Copy link

mburbea commented Mar 16, 2015

What performance issue? Is it because it is a class type as opposed to a struct?

@WolfieWerewolf
Copy link

Couldn’t the Tuple be re-written as a value type?

On Mar 16, 2015, at 2:10 PM, Michael Burbea [email protected] wrote:

se it is a class type as op

@HaloFour
Copy link

@mburbea Yep, Tuple<> and it's family are classes.

@WolfieWerewolf Currently Tuple<T1,T2> inherits from Tuple<T1> and Tuple<T1, T2, T3> inherits from Tuple<T1, T2>, etc., so no. I was wrong, they don't.

@paulomorgado
Copy link

@HaloFour, where have you seen that "currently Tuple<T1,T2> inherits from Tuple<T1> and Tuple<T1, T2, T3> inherits from Tuple<T1, T2>, etc." ?

@Miista
Copy link

Miista commented Mar 16, 2015

@HaloFour I don't know of any language that supports tuples where the tuples end up in a public api.

@WolfieWerewolf
Copy link

Is that a reason not to do it?

On Mar 16, 2015, at 2:35 PM, Søren Palmund [email protected] wrote:

es end up in a public api.

@HaloFour
Copy link

@paulomorgado Ah crap, you're right. I was thinking of another lib I had worked with recently where tuple types did inherit like that.

@Miista
Copy link

Miista commented Mar 16, 2015

@WolfieWerewolf is that a reason not to implement tuples or? I'm not sure what you're asking. I believe that it's up to the individual developer his he wants to design his api. If he wants to return tuples all the way that's his decision. Of course we should try to discourage "wrong" use of a language feature but we must also recognize that almost any feature can be misused to some extent.

@gafter
Copy link
Member

gafter commented Mar 16, 2015

@danfma We are leaning toward a solution that does unify across assembly boundaries.

@vladd
Copy link

vladd commented Mar 16, 2015

@Miista About returning tuples, it's quite common in Python. The accepted answer suggests that the named tuples are often preferred in Python over the plain ones.

@eatdrinksleepcode
Copy link

The argument for tuple parts needing names because of unintuitive method return values is interesting. We don't currently have any way of specifying a name for the return value of a method, and there are certainly plenty of ways to return an unintuitive value from a method. Such cases would be considered a code smell, the proper remediation of which would be to rename the method such that the return value is clear. Why would the same not be true of tuple return values?

@MgSam
Copy link

MgSam commented Mar 17, 2015

I agree with the comments saying its weird that return types would be named for tuples but are not named in the case of a single return value. However, I don't think the solution to this is to make tuples less useful by not having names. I think the natural solution would be allowing all return values to be named- it would both lead to more self-documenting code and provide parity with the specialness being proposed for tuples.

@ashmind
Copy link
Contributor

ashmind commented Mar 17, 2015

We don't currently have any way of specifying a name for the return value of a method

But that doesn't change with named tuples.

E.g. what you had previously:

public SomeResult GetSomething() { ... }
var result = DoSomething();
if (result.Success)
    Console.WriteLine(result.Value);

What you have with tuples:

public (bool success, int value) GetSomething() { ... }
var result = DoSomething();
if (result.success)
    Console.WriteLine(result.value);

Total amount of names is exactly the same aside from the type name.

All I want is to avoid total regression where the property names are lost as well.

@qrli
Copy link

qrli commented Mar 17, 2015

For the public API returning tuple use case:

  • First, as soon as we have record class, the return record can be declared as easy as
    record SomeResult(bool Success, int Value)
    Then named tuple will not have a big advantage here, or it could be worse by repeating the named tuple declarations.
  • Second, for public API, you will have more concerns, such as versioning. For a struct (and assume record class, too), it would be easier to add a new member while keeping backward-compatibility. For a tuple, you cannot. Though, you can still workaround this by providing method overloads, just not that clean.
  • Third, for public API, you would expect languages other than C# to be able to consume it, so they must support the same tuple, instead of a C#-specific one.

@Miista
Copy link

Miista commented Mar 17, 2015

My thoughts too.


Søren Palmund

Den 17/03/2015 kl. 08.14 skrev qrli [email protected]:

For the public API returning tuple use case:

First, as soon as we have record class, the return record can be declared as easy as
record SomeResult(bool Success, int Value)
Then named tuple will not have a big advantage here, or it could be worse by repeating the named tuple declarations.

Second, for public API, you will have more concerns, such as versioning. For a struct (and assume record class, too), it would be easier to add a new member while keeping backward-compatibility. For a tuple, you cannot. Though, you can still workaround this by providing method overloads, just not that clean.

Third, for public API, you would expect languages other than C# to be able to consume it, so they must support the same tuple, instead of a C#-specific one.


Reply to this email directly or view it on GitHub.

@HaloFour
Copy link

@qrli

  1. You're correct that the short-hand for a "record" class does eliminate much of the usefulness of named tuples, but "record" classes and tuples are the same in that they're both nothing more than shorthand over the existing type system.
  2. This is no different than changing the return type of any method. And you can't overload on the return type in C#. But if tuples were simply shorthand for declaring records and classes you'd have the option to "promote" the tuple to a properly-defined type and not break the contract.
  3. A type is a type, according to CLS. It doesn't matter if it's already in the BCL or emitted by the compiler into the assembly.

I'd personally like to see both methods implemented. That covers both mathematic tuples and relational model tuples.

If unnamed tuples was just a short-hand over Tuple<> that's probably fine, although you don't gain a whole lot in terms of typing and you still have the issue of those types being reference types. That could be solved by adding another family of tuple types to the BCL that are structs.

Named tuples could just be inline record classes, with either convention or syntax to specify the name of the type. The problem here is type-equivalence as the BCL would treat each of those types as completely separate for now.

Really, in my opinion, the implementation of the tuple is much less important than the language syntax to support working with them. I doubt that tuples in C# would enjoy the same degree of support as they do in languages like Python where they are fairly pervasive. I imagine that we'll probably see about the same level of support as with Apple Swift, with basic deconstruction into variables and probably pattern matching.

@qrli
Copy link

qrli commented Mar 17, 2015

@HaloFour : I don't think it is easy to make named tuples as simple syntax sugar over normal struct/class. tuple implies some specific behavior on them, such as member order, which is important when unpacking:
(var success, var value) = GetSomeResult();
It is not so easy/intuitive to write a normal struct/class to replace it in a fully compatible way.

But I fully agree with you that the syntax is much more useful than tuple itself. Like @MikePopoloski 's comment, we are more on the performance/maintainability side. Maybe what we want is not tuple at all, but a better syntax for out parameters.

However, I don't think
(var ret, var outParam1, var outParam2) = FunctionWithOutParamaters()
is better than
var ret = FunctionWithOutParameters(var outParam1, var outParam2).
First, there will be overload resolution issue if destructral assignment was also supported for records. Second, you lose IntelliSense. Third, the syntax is not simpler at all.

@HaloFour
Copy link

@qrli

They're going to be syntax sugar over struct/class either way simply because those are the only types possible in the CLR. It's not like you can push two values onto the evaluation stack and then pop them at the caller. The CLR doesn't allow that. The question is whether they use Tuple<> or another shared general-purpose type or if the compiler generates its own types.

When dealing with named tuples as return types I envision the primary use would be to treat the return value as a single value and the named properties as properties. This is in line with how they're generally used in Apple Swift and Python:

let http200Status = (statusCode: 200, description: "OK")
println("The status code is \(http200Status.statusCode)")
// prints "The status code is 200"
println("The status message is \(http200Status.description)")
// prints "The status message is OK"

But I think that even with named tuples that the order of the components is important as I think that said tuple should be accessible by both name and index. It would also matter for destructuring expressions.

let http404Error = (statusCode: 404, description: "Not Found")
let (code, message) = http404Error

Or it can go the EcmaScript route where the named tuple properties are accessed by name within the destructuring assignment:

var http204status = { statusCode: 204, description: "No Content" };
var { statusCode : code, description : message } = http204status;
var message = `The status code is ${code} meaning ${message}`;

Personally I think that out parameters should be left out of the equation. Trying to come up with a syntax to magickally pretend that they're tuples will just be dirty and confusing to anyone trying to figure out how a method is being called. If tuples demonstrate their value above and beyond the current use of out parameters then I think the BCL should adopt tupled overloads of those methods.

@qrli
Copy link

qrli commented Mar 18, 2015

@HaloFour If tuple were syntax sugar to normal class/struct, that means normal class/struct also need to support structural equality across assemble boundary, which means new MyVector(x, y, z) == new SomeLibrary.Vector(x, y, z). I'm not sure if it is good or bad, as it opens a backdoor to the type system, which may introduce security concerns. Personally I'm interested in such ability though, as I convert from different vector types a lot. :P

Also, member order would become important for normal class/struct, reorder members can be a breaking change.

@gafter
Copy link
Member

gafter commented Mar 20, 2015

Our current thinking is that we'll use a set of platform-wide structs analogous to the existing Tuple classes, and encode the field names as attributes (i.e. they will be erased in the runtime representation). Thus we would not need CLR support as @qrli fears.

@ashmind
Copy link
Contributor

ashmind commented Mar 20, 2015

Our current thinking is that we'll use a set of platform-wide structs analogous to the existing Tuple classes, and encode the field names as attributes (i.e. they will be erased in the runtime representation).

I suggest implementing IDynamicMetaObjectProvider if you do that, for sane dynamic support. Hm, no, wait, that wouldn't work unless struct also has a list of names.

It's sad that CLR can support [TypeIdentifier] for edge-case COM scenarios, but not for common-case things like function and tuple types. Currently it isn't even possible to make a language with generic delegates (F# has to apply some hacks to get it working).

@HaloFour
Copy link

@gafter

So that means that the following two snippets would be effectively equivalent:

public (int quotient, int remainder) divmod(int numerator, int denominator)
{
    int quotient = numerator / denominator;
    int remainder = numerator % denominator;
    return (quotient, remainder);
}

static void Main()
{
    var result = divmod(5, 2);
    Console.WriteLine("5 / 2 is {0} with a remainder of {1}", result.quotient, result.remainder);
}
[TupleNames("quotient", "remainder")]
public STuple<int, int> divmod(int numerator, int denominator)
{
    int quotient = numerator / denominator;
    int remainder = numerator % denominator;
    return new STuple<int, int>(quotient, remainder);
}

static void Main()
{
    STuple<int, int> result = divmod(5, 2);
    Console.WriteLine("5 / 2 is {0} with a remainder of {1}", result.Item1, result.Item2);
}

@HaloFour
Copy link

@ashmind Well, it could if the function creating the struct supplied it with the names:

return new STuple<int, int>("quotient", quotient, "remainder", remainder);

Feels kludgy, but without CLR support for real tuples we're probably stuck with kludgy.

@svick
Copy link
Contributor

svick commented Mar 20, 2015

@HaloFour The problem with that is that it would mean STuple<int, int> is twice (x86) or three times (x64) as big as it could have been, to support a pretty rare (I assume) scenario.

@gafter
Copy link
Member

gafter commented Nov 21, 2015

The design notes have been moved to https://github.com/dotnet/roslyn/blob/master/docs/designNotes/2015-02-11%20C%23%20Design%20Meeting.md

Discussion on these design notes are in this issue.

@gafter gafter closed this as completed Nov 21, 2015
@redradist
Copy link

Check approach to destructible struct-s in dotnet/csharplang#4808

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests