Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Locked thread
Dietrich
Sep 11, 2001

Munkeymon, I think we're arguing two different things right now.

The main point I'm trying to make is that method signatures that are very long are almost always indicative of some form of God Function that does way too much.

The solution to this problem is not just to take those very long method signatures and put them into a class, then pass an instance of the class, that would be silly.

The signature is just the symptom, the God Function is the problem. Fix the problem.

Again, without a specific situation it's hard to give concrete examples of the best way to fix a God Function, but there is almost always a better way to architect your program that will improve test-ability, readability, maintainability, and usability.

Adbot
ADBOT LOVES YOU

RICHUNCLEPENNYBAGS
Dec 21, 2010
That's all well and good but I think it's a little bit simplistic to say any code that is easier to read with named parameters is wrong. If that were really the case why would they have added such a feature at all?

Zhentar
Sep 28, 2003

Brilliant Master Genius
To ease interoperability with with APIs that aren't able to leverage overloading (e.g. COM).

RICHUNCLEPENNYBAGS
Dec 21, 2010
Well, fair enough, I suppose. I like to use them even when there aren't a lot of arguments. Particularly with bools because it's not obvious what you were specifying when you put "false" in something.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

Munkeymon posted:

Look at Bognar's post about not passing models around.

Yeah, I was definitely just referring to data models there. If you have a parameter object that's specific to your function, then you don't run into the problems I was bitching about (e.g. modifying a data model can cause unexpected behavior in your methods that include it in its signature).

I'm not really a fan of parameter objects in general, though, mainly for the reason that if I change a method and add or remove a parameter then my code could still compile while being incorrect and defer the errors to runtime. If my method signature instead lists all of its requirements explicitly, I can't compile the code until I fix everything that the change broke. Obviously you should still have the runtime checks for invalid data being passed in, but I like to lean on the compiler wherever possible. This isn't exactly relevant in the situation of optional/named parameters, but I would still prefer those to parameter objects.

RICHUNCLEPENNYBAGS
Dec 21, 2010
I can't say I'm a huge fan of parameter objects either; for all the reasons you've discussed. And in any case it's hardly getting at the root of the problem (assuming it is one and you don't have a legitimate need for lots of parameters).

Dietrich
Sep 11, 2001

I don't frequently use "parameter objects", but when I do, they do a lot more than just being a struct to pass data around.

Macichne Leainig
Jul 26, 2012

by VG
Someone help me wrap my head around this loving thing I'm looking at.

Some background on what the control does (I can't really speak about in in depth for confidentiality reasons) is that you can enter a keyword like "payment" in this textbox, and there's a dropdown right next to the combobox that populates with all of these database columns whose metadata contains a description of the word "payment."

But the code behind is literally just this for these two controls.

code:
public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter",
                                                                    typeof(string),
                                                                    typeof(KeyValueNullableFilterableSelectElement));

public string Filter
{
    get { return (string)GetValue(FilterProperty); }
    set
    {
        SetValue(FilterProperty, value);
    }
}

public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue",
                                                                                    typeof(KeyValuePair<int, string>?),
                                                                                    typeof(KeyValueNullableFilterableSelectElement));

public KeyValuePair<int,string>? SelectedValue
{
    get { return (KeyValuePair<int,string>?)GetValue(SelectedValueProperty); }
    set
    {
        SetValue(SelectedValueProperty, value);
    }
}
The xaml looks like this for the Filter textbox, and the dropdown is similarly simple:

code:
<TextBox Text="{Binding Filter, Mode=TwoWay}"/>
That's literally it. It gets passed a datacontext in other xaml's but I can see no interaction logic whatsoever, and a ReSharper "Find Usage" results in gently caress all.

Can someone explain to me likely how this works? I want to move this filter out of the usercontrol and have a dedicated lookup elsewhere in the UI.

e: poo poo, I found it. It's not quite some magic anymore. I thought the DependencyProperty was doing something magical.

Macichne Leainig fucked around with this message at 16:51 on Jan 17, 2014

Munkeymon
Aug 14, 2003

Motherfucker's got an
armor-piercing crowbar! Rigoddamndicu𝜆ous.



Dietrich posted:

Munkeymon, I think we're arguing two different things right now.

The main point I'm trying to make is that method signatures that are very long are almost always indicative of some form of God Function that does way too much.

The solution to this problem is not just to take those very long method signatures and put them into a class, then pass an instance of the class, that would be silly.

I guess we were, but I'd like to point out that this is the entirety of what I was disagreeing with:

quote:

it could benefit from passing in an object that encapsulates the necessary arguments

Which I think you just called silly?

I really don't like parameter objects, is all :colbert:

Dietrich
Sep 11, 2001

Munkeymon posted:

I guess we were, but I'd like to point out that this is the entirety of what I was disagreeing with:


Which I think you just called silly?

I really don't like parameter objects, is all :colbert:

Let me clarify.

I think outsourcing argument validation from the body of an overly-long method would be a beneficial change. I think we then got into a discussion about compiler "you missed an argument" errors versus my point that argument validation needs to be about more than just missing arguments.

But that was leading us away from my primary point, which was that I think an even more beneficial change would be getting rid of the overly-long method.

ljw1004
Jan 18, 2005

rum

Dietrich posted:

The main point I'm trying to make is that method signatures that are very long are almost always indicative of some form of God Function that does way too much.

I'd agree with that for object-oriented design.

But for functional programming? where everything really is a function? and the essence of the problem is that you have something that takes in a bunch of stuff and gives out an answer? then I think that lots of parameters are a true (and therefore good) way to represent the problem.


One other thing to note is that parameter objects and named arguments can be pretty similar. For instance,
code:
' Pattern used a bit in MVC. It uses anonymous types for a lightweight way
' to construct parameter objects. Unfortunately the foo has to use reflection to get them.
foo(New With {.param1="hello", .param2=15, .param3="world"})


// Pattern I often see in Python
var formattedString = "Hello {x} and {y}".FormatString( {x:="fred", y:="jones"})

Dr Monkeysee
Oct 11, 2002

just a fox like a hundred thousand others
Nap Ghost
All this named parameter talk has obscured the fact that among all those nice C# 6 features the multiple return values example is a big ugly lump. wtf is that syntax.

Sedro
Dec 31, 2008

Monkeyseesaw posted:

All this named parameter talk has obscured the fact that among all those nice C# 6 features the multiple return values example is a big ugly lump. wtf is that syntax.
Really, all the language needs is some syntactic sugar for tuples, and that would be useful for more than just multiple return values. I shouldn't ever have to write Tuple.Create or tuple.Item1, tuple.Item2. They probably wanted to leverage the multiple return capabilities of the CLR.

And the null coalesce syntax? Hooray, an untyped maybe monad that throws NREs.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

Sedro posted:

Really, all the language needs is some syntactic sugar for tuples, and that would be useful for more than just multiple return values. I shouldn't ever have to write Tuple.Create or tuple.Item1, tuple.Item2. They probably wanted to leverage the multiple return capabilities of the CLR.

We were talking about this at work today and my co-worker convinced me that being able to return anonymous types would be a better solution. It's a bit quirky, but it allows you to have named properties instead of just relying on the position in the tuple. It might look like:

C# code:
public var GetMostUsedCharacter(string input) {
	var mostUsed = input.GroupBy(c => c).OrderByDescending(g => g.Count()).First();
	return new { Letter = mostUsed.Key, Count = mostUsed.Count() };
}
It basically gives you everything that a tuple does, but adds named properties and employs a more commonly used language feature (I rarely see Tuple<>, but anonymous types are everywhere).

Sedro posted:

And the null coalesce syntax? Hooray, an untyped maybe monad that throws NREs.

I'm not sure I follow here. Isn't the point that it stops evaluating the expression once it encounters a null and thus doesn't throw NREs?

Bognar fucked around with this message at 03:29 on Jan 18, 2014

RICHUNCLEPENNYBAGS
Dec 21, 2010

Bognar posted:

We were talking about this at work today and my co-worker convinced me that being able to return anonymous types would be a better solution. It's a bit quirky, but it allows you to have named properties instead of just relying on the position in the tuple. It might look like:

C# code:
public var GetMostUsedCharacter(string input) {
	var mostUsed = input.GroupBy(c => c).OrderByDescending(g => g.Count()).First();
	return new { Letter = mostUsed.Key, Count = mostUsed.Count() };
}
It basically gives you everything that a tuple does, but adds named properties and employs a more commonly used language feature (I rarely see Tuple<>, but anonymous types are everywhere).


I'm not sure I follow here. Isn't the point that it stops evaluating the expression once it encounters a null and thus doesn't throw NREs?

So return a dynamic then.

I mean I know people are afraid of them but they're useful for any number of things.

Sedro
Dec 31, 2008

Bognar posted:

C# code:
public var GetMostUsedCharacter(string input) {
	var mostUsed = input.GroupBy(c => c).OrderByDescending(g => g.Count()).First();
	return new { Letter = mostUsed.Key, Count = mostUsed.Count() };
}
They made some mention of structural types. In your example the return type would be something like { string Letter, int Count }.

Bognar posted:

I'm not sure I follow here. Isn't the point that it stops evaluating the expression once it encounters a null and thus doesn't throw NREs?
Well, in a language where any reference can be null, everything is a maybe, and you're not going to change every dereference into a maybe-dereference. So without the help of the compiler you have to read through API docs to see if things return null. And if you mess one up, NRE.

Obviously they can't fix null at this point, but advocating its use through language features seems like a bad idea.

ninjeff
Jan 19, 2004

Sedro posted:

Obviously they can't fix null at this point, but advocating its use through language features seems like a bad idea.

I honestly didn't expect to see prohibitionism brought up regarding programming language features, but I suppose it makes sense in retrospect. Letting people deal with nulls more cleanly isn't going to cause panic in the streets.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

RICHUNCLEPENNYBAGS posted:

So return a dynamic then.

I mean I know people are afraid of them but they're useful for any number of things.

I honestly wish dynamics had never been added. I get that they're useful for making COM interop code not look terrible, but I feel like the solution is to build a sane wrapper API around common COM interaction rather than adding duck typing to a statically typed language. I don't have a problem with duck typing in and of itself, I just don't want it littered throughout my statically typed code. The compiler will check my errors in all my statically typed code, but the compiler only looks like it's checking my errors in dynamically typed code.

Also, just by virtue of having it as a language feature people will try to use it in places where it shouldn't be. This is one of those places.

Sedro posted:

They made some mention of structural types. In your example the return type would be something like { string Letter, int Count }.

I don't remember seeing this in the stuff I read, but I would be very pleased if that was added.

Sedro posted:

Well, in a language where any reference can be null, everything is a maybe, and you're not going to change every dereference into a maybe-dereference. So without the help of the compiler you have to read through API docs to see if things return null. And if you mess one up, NRE.

Obviously they can't fix null at this point, but advocating its use through language features seems like a bad idea.

I don't think that this new feature encourages people to use null any more than they already do. It's already everywhere and strictly unavoidable due to the language design, so this is just syntactic sugar to make working around it suck less.

Bognar fucked around with this message at 05:40 on Jan 18, 2014

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice

Munkeymon posted:

It's not just you. Their pidgin SQL just gives me a headache because it's not SQL but looks similar enough that it requires extra mental effort to reason about what it's doing differently. At least that's what I think is going on. At any rate, I always write LINQ in object.Linq(lambda or function call) style because I find it a lot easier to both read and write.

But, hey, maybe I'm (more) broken (than most people) :v:

I can never remember how to do left joins in LINQ, among other things, for the same reason. However when you have a query like this the query comprehension syntax is a heck of a lot better than manually doing the key selection and whatnot for grouping:

code:
            
            var q = (from s in context.Subjects
                     join c in context.CatalogItems on s.Id equals c.SubjectId
                     join e in context.InventoryEditions on c.EditionId equals e.Id
                     where c.UnitNumber != null
                     && c.IsAssignable == true
                     group new { CatalogItem = c, InventoryEdition = e } 
                     by new { SubjectId = c.SubjectId, UnitNumber = c.UnitNumber } into grp
                     select new { SubjectId = grp.Key.SubjectId, 
                       UnitNumber = grp.Key.UnitNumber, Priority = grp.Max(x => x.InventoryEdition.Priority) });

            var q2 = from z in q
                     join e in context.InventoryEditions on z.Priority equals e.Priority
                     select new { SubjectId = z.SubjectId, UnitNumber = z.UnitNumber, EditionId = e.Id };

            var tempCatalog = from c in context.CatalogItems
                              where !c.IsRetired
                                && c.SubjectId != null
                                && c.IsAssignable == true
                              select c;

            var catalogItems = (from s in subs
                                join ed in q2 on new { SubjectId = s.SubjectId, Unit = s.UnitNumber } 
                               equals new { SubjectId = ed.SubjectId, Unit = ed.UnitNumber } into edList
                                from edition in edList.DefaultIfEmpty()
                                join sub in context.Subjects on s.SubjectId equals sub.Id
                                join catItem in tempCatalog on new { SubjectId = sub.Id, Unit = s.UnitNumber }
                               equals new { SubjectId = catItem.SubjectId.Value, Unit = catItem.UnitNumber }
                                where catItem.VendorId == systemVendorId
                                    && (catItem.EditionId == null || 
                                          (edition != null && edition.EditionId == catItem.EditionId))
                                group s by catItem.Id into mygrp
                                select new StudentSubjectInventoryGroup
                                {
                                    CatalogItemId = mygrp.Key,
                                    Quantity = mygrp.Count(),
                                    StudentSubjects = mygrp.Select(x => new StudentSubjectInventoryDetail
                                    {
                                        StudentSubject = x
                                    }).ToList()
                                }).ToList();
I'm not proud of this code, but it has to satisfy some Byzantine requirements. Doing it with extension methods would be even less clear (at least to me).

Simulated fucked around with this message at 18:13 on Jan 18, 2014

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice

ljw1004 posted:

Oh yes, it's getting incorporated into VB (I'm the VB design lead). I blogged about a few bits and pieces...
http://blogs.msdn.com/b/lucian/archive/2013/12/13/some-potential-language-ideas-for-future-versions-of-vb.aspx

Does the current C# design lead have a blog? Eric Lippert used to be my goto for that sort of thing.

Please, for the love of StackOveflow, make Edit & Continue support Lambdas, anonymous types, etc. The inability to use it with any method containing those things combined with LINQ has effectively killed that feature for me and it was so drat handy for debugging (when it takes 15 minutes for your process to load several gigabytes from the database having to stop, build, and restart is a massive waste of time).


Otherwise I am terribly excited about language support for immutability and I pray it happens. We are doing a sort of copy-on-write with immutable objects to support highly concurrent access to these multi-gigabyte bits of data and it is a billion times better than locking, even though we have to handle all the immutability by hand.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Ender.uNF posted:

(when it takes 15 minutes for your process to load several gigabytes from the database

Maybe you should be debugging against a smaller data set? Or writing some unit testing your logic so you don't have to deal with databases at all?

zokie
Feb 13, 2006

Out of many, Sweden
And here I thought dynamic was about working with JSON easier or when you don't feel the need to create an interface for some shared properties or whatever...

I guess I should be glad I don't have to deal with COM stuff...

Dietrich
Sep 11, 2001

Ender.uNF posted:

I can never remember how to do left joins in LINQ, among other things, for the same reason. However when you have a query like this the query comprehension syntax is a heck of a lot better than manually doing the key selection and whatnot for grouping:

code:
            
            Crazy rear end linq query
                   
I'm not proud of this code, but it has to satisfy some Byzantine requirements. Doing it with extension methods would be even less clear (at least to me).

I think I would have handled that with a view or stored procedure. Good lord.

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice

Ithaqua posted:

Maybe you should be debugging against a smaller data set? Or writing some unit testing your logic so you don't have to deal with databases at all?

For normal day-to-day debugging, that is what we do. But customers run with large data sets in the real world and the only way to be sure you don't fall down under that kind of load is to use it the same way. Plus we are heavily metadata-driven, which then acts off the user data so sometimes bugs can only be replicated by running a copy of the customer's database.

Just to give some scope: We have an embedded JavaScript engine that fires customer-written validation scripts in response to realtime user actions. We have an in-memory transaction manager that knows how to roll-back changes to objects in memory (which we are dumping for a copy-on-write system). We do diffs and merges between these data sets... Imagine your entire file system is one git repo and now you want to do a diff and merge between a million+ files and each file's thousand bits of metadata, including tracking moves, deletes, renames, etc. Oh and the metadata has changed since the older snapshot was made. And some of the metadata is actually a piece of JavaScript that calculates the value dynamically because it is NP-hard to track dependencies closely enough to know when to invalidate cached values on writes, not to mention the space/lookup tradeoffs it would require.

I don't think I will ever work on a piece of software this complicated again in my lifetime.

RICHUNCLEPENNYBAGS
Dec 21, 2010

Bognar posted:

I honestly wish dynamics had never been added. I get that they're useful for making COM interop code not look terrible, but I feel like the solution is to build a sane wrapper API around common COM interaction rather than adding duck typing to a statically typed language. I don't have a problem with duck typing in and of itself, I just don't want it littered throughout my statically typed code. The compiler will check my errors in all my statically typed code, but the compiler only looks like it's checking my errors in dynamically typed code.

Dynamic dispatch is a really useful idiom, anyway. I don't think anonymous types are really all that much "better" than dynamics in any of the aspects that bother you about dynamics.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

RICHUNCLEPENNYBAGS posted:

Dynamic dispatch is a really useful idiom, anyway. I don't think anonymous types are really all that much "better" than dynamics in any of the aspects that bother you about dynamics.

Except that anonymous types offer compile time type safety.

ManoliIsFat
Oct 4, 2002

RICHUNCLEPENNYBAGS posted:

Dynamic dispatch is a really useful idiom, anyway. I don't think anonymous types are really all that much "better" than dynamics in any of the aspects that bother you about dynamics.

Well anonymous types are just syntactical sugar, right? The compilers just autogenerating a class for me.

RICHUNCLEPENNYBAGS
Dec 21, 2010

Bognar posted:

Except that anonymous types offer compile time type safety.

I just don't see what kind of sense it makes returning them. If you think you're going to need the object in multiple places you should actually define it. Otherwise it seems to me you're getting an inappropriate level of coupling between methods.

As for type safety, if you want you can cast a dynamic to get Intellisense or whatever. But to be honest I think the chances of this kind of error aren't super-high and also they're going to be really obvious when you run across them.

RICHUNCLEPENNYBAGS fucked around with this message at 17:54 on Jan 19, 2014

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice

RICHUNCLEPENNYBAGS posted:

I just don't see what kind of sense it makes returning them. If you think you're going to need the object in multiple places you should actually define it. Otherwise it seems to me you're getting an inappropriate level of coupling between methods.

As for type safety, if you want you can cast a dynamic to get Intellisense or whatever. But to be honest I think the chances of this kind of error aren't super-high and also they're going to be really obvious when you run across them.

You are coupled to the method by definition. If I change the return type from byte[] to int, bool to float, or Frobber to Gobulator you're just as tightly coupled as if I were returning { x, y }. If I change the parameter list, other than adding new optional parameters or some limited type changes, all my callers must be updated. If I add, remove, or change out/ref parameters I just broke your poo poo yo.

Same would apply to returning an anonymous type: anything other than adding new properties is a breaking change. Yes, a class must be defined... But let the compiler worry about it.

You can certainly create iterators without yield return but why the hell would you? Let the compiler create that class for you.


"you should define it" automatically smells like bullshit to me because you can apply that argument to everything besides pure assembly. No, actually, I shouldn't have to define it when the compiler can figure it out automatically.

Gul Banana
Nov 28, 2003

basically a Tuple<int,string> is not actually more 'defined' than a hypothetical {.NamedInt,.NamedString} structural type. it's just less convenient.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

RICHUNCLEPENNYBAGS posted:

I just don't see what kind of sense it makes returning them. If you think you're going to need the object in multiple places you should actually define it.

It's just syntactic sugar. Defining a class seems silly when it does nothing more than hold a few properties of data, but it's the best option if you want to have multiple return types. Tuples are the go-to method of handling this in many other languages because those languages have terse syntax for creating them. C# is getting better about Tuple creation in the next iteration, but ultimately it seems like it's inferior to defining an anonymous type. Anonymous types have a terse syntax and named properties, I can already use them locally in my code to great effect, so why not be able to return them as well?

RICHUNCLEPENNYBAGS posted:

As for type safety, if you want you can cast a dynamic to get Intellisense or whatever.

Casting still isn't safe. You're telling the compiler "Yeah, this is what I think that variable will be" rather than the compiler knowing "this is exactly what that variable is". Also, if you're returning a dynamic because you didn't want to define a small class, what exactly are you going to cast the dynamic to?

RICHUNCLEPENNYBAGS posted:

But to be honest I think the chances of this kind of error aren't super-high and also they're going to be really obvious when you run across them.

Sure, when you run across them at runtime as opposed to never having the problem at all because the compiler handled it all for you. I don't understand why you would willingly throw away the compiler's error checking when it's basically free.

EDIT:

Here's some example code:

C# code:
// Anonymous type return value
public var GetMostUsedCharacterAnonymousType(string input) {
	var mostUsed = input.GroupBy(c => c).OrderByDescending(g => g.Count()).First();
	return new { Letter = mostUsed.Key, Count = mostUsed.Count() };
}

public void CompilerError(){
	var mostUsed = GetMostUsedCharacterAnonymousType("testtest");
	Console.WriteLine("{0}: {1}", mostUsed.Letter, mostUsed.Coutn);
}

// Dynamic type return value
public dynamic GetMostUsedCharacterDynamic(string input) {
	var mostUsed = input.GroupBy(c => c).OrderByDescending(g => g.Count()).First();
	return new ExpandoObject { Letter = mostUsed.Key, Count = mostUsed.Count() };
}

public void RuntimeError(){
	var mostUsed = GetMostUsedCharacterDynamic("testtest");
	Console.WriteLine("{0}: {1}", mostUsed.Letter, mostUsed.Coutn);
}
The code is almost exactly the same, yet one fails as soon as you press F5. Why would I pick the other?

Bognar fucked around with this message at 22:11 on Jan 19, 2014

ljw1004
Jan 18, 2005

rum

Ender.uNF posted:

Same would apply to returning an anonymous type

Allowing anonymous type returns is something that's been discussed in C# and VB language design meetings, but rejected.


* It's clear that just returning tuples isn't enough, because when developers write "var (item1,item2,item3,item4) = foo()" they always forget which item is which, and they long for self-describing structures.


* The types of anonymous types can't be expressed as types in C# or VB. Therefore, if we were to allow methods to return anonymous types, we'd have to open the door to implicit typing for any members.
code:
class C {
   var f() { return new {x=15, y=28}; }
}
This would go down like a lead balloon :) even now at conferences people will ask us "What are your recommendations about 'var'?" hoping to be able to go back and tell their colleagues that even the VB/C# team wants them to stop using 'var'. Of course we never oblige because we think 'var' is fine :) Letting people use 'var' in public signatures of APIs would however be a step too far.


* Implicit return types would also open the door to circularity! Even aside from circularity, C#/VB would need whole new sets of rules about the order in which members' types are resolved. (including with partial types).
code:
class C {
   var f() { return g(); }
   var g() { return f(); }
}
* The end result is that being able to return tuples or anonymous types is an interesting idea but not quite right. What's needed is a strongly-typed way to return a collection of named values. We haven't yet seen a good enough syntax for this. F# lets you do it but in a kind of mysterious way. Nothing seems substantially better than the current poor workarounds (1) returning your own type, (2) using out parameters, (3) returning tuples.


Bognar posted:

C# is getting better about Tuple creation in the next iteration

No! Mads mentioned one possibility that has been raised but it's far from settled and not obvious that it's better or will be adopted. Mads described the idea that

* When you write a constructor call "new Foo(args)" then, if there doesn't exist a nongeneric "class Foo" but there does exist one or more generic types like "class Foo<T>" or "class Foo<T,U>" then it will perform overload resolution on the constructors of all of those types to infer T.

This would let you write "new Tuple(x,y)" in every place that you currently write "Tuple.Create(x,y)" and that's all the help it would provide for tuples. It would save you exactly four keystrokes every time you wanted construct a tuple. That's not much help. It wouldn't help the more common cases, e.g.

code:
class C
{
  Dictionary<CustomerID, CustomerContactDetails> d = new Dictionary<CustomerID, CustomerContactDetails>();
  // you'd still have to write this tedious type out twice

  Tuple<string,Exception> GetError(string s)
  {
    if (s == null) return Tuple.Create(null, null);
    // it wouldn't make it any easier to return this kind of tuple
    ...
  }
}

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice

ljw1004 posted:

Allowing anonymous type returns is something that's been discussed in C# and VB language design meetings, but rejected.


* It's clear that just returning tuples isn't enough, because when developers write "var (item1,item2,item3,item4) = foo()" they always forget which item is which, and they long for self-describing structures.


* The types of anonymous types can't be expressed as types in C# or VB. Therefore, if we were to allow methods to return anonymous types, we'd have to open the door to implicit typing for any members.
code:
class C {
   var f() { return new {x=15, y=28}; }
}
This would go down like a lead balloon :) even now at conferences people will ask us "What are your recommendations about 'var'?" hoping to be able to go back and tell their colleagues that even the VB/C# team wants them to stop using 'var'. Of course we never oblige because we think 'var' is fine :) Letting people use 'var' in public signatures of APIs would however be a step too far.

OK, I agree about nameless values but I disagree... I am not asking for var to end up in the ultimate public signature of an API. What I am asking for is to have the compiler treat return new { x=5, y=farts } as a public class with auto-properties x,y, each having the type inferred from the values if known at compile type, or ultimately object if it can't be deduced (or maybe you require the type be specified if it can't be inferred). So from the point of view of a consumer of this method, it has a type, just not one manually defined. Treating any instance of a similar type but with an additional member as inheriting from that type so you can add new members without breaking existing users would just be icing on the cake.

People are doing this stuff now, they just use brittle stuff like dictionaries and lists to transport the values.



quote:

* Implicit return types would also open the door to circularity! Even aside from circularity, C#/VB would need whole new sets of rules about the order in which members' types are resolved. (including with partial types).
code:
class C {
   var f() { return g(); }
   var g() { return f(); }
}
* The end result is that being able to return tuples or anonymous types is an interesting idea but not quite right. What's needed is a strongly-typed way to return a collection of named values. We haven't yet seen a good enough syntax for this. F# lets you do it but in a kind of mysterious way. Nothing seems substantially better than the current poor workarounds (1) returning your own type, (2) using out parameters, (3) returning tuples.

So what happens if I create classes A : B and B: A today? That's easily identified as an error.

I agree that something different from an anonymous class or an automatically defined class is probably ideal because the semantics are more like prototype-based languages, except the vast majority of the time the type of all the values will be known with reasonable specificity at compile time.

RICHUNCLEPENNYBAGS
Dec 21, 2010

Ender.uNF posted:

You are coupled to the method by definition. If I change the return type from byte[] to int, bool to float, or Frobber to Gobulator you're just as tightly coupled as if I were returning { x, y }. If I change the parameter list, other than adding new optional parameters or some limited type changes, all my callers must be updated. If I add, remove, or change out/ref parameters I just broke your poo poo yo.

Same would apply to returning an anonymous type: anything other than adding new properties is a breaking change. Yes, a class must be defined... But let the compiler worry about it.

You can certainly create iterators without yield return but why the hell would you? Let the compiler create that class for you.


"you should define it" automatically smells like bullshit to me because you can apply that argument to everything besides pure assembly. No, actually, I shouldn't have to define it when the compiler can figure it out automatically.

Except now instead of reading a class definition to find out the properties of an object, you're going and reading through a method. This isn't really good design.

RICHUNCLEPENNYBAGS
Dec 21, 2010

Bognar posted:

Sure, when you run across them at runtime as opposed to never having the problem at all because the compiler handled it all for you. I don't understand why you would willingly throw away the compiler's error checking when it's basically free.

EDIT:

Here's some example code:

C# code:
// Anonymous type return value
public var GetMostUsedCharacterAnonymousType(string input) {
	var mostUsed = input.GroupBy(c => c).OrderByDescending(g => g.Count()).First();
	return new { Letter = mostUsed.Key, Count = mostUsed.Count() };
}

public void CompilerError(){
	var mostUsed = GetMostUsedCharacterAnonymousType("testtest");
	Console.WriteLine("{0}: {1}", mostUsed.Letter, mostUsed.Coutn);
}

// Dynamic type return value
public dynamic GetMostUsedCharacterDynamic(string input) {
	var mostUsed = input.GroupBy(c => c).OrderByDescending(g => g.Count()).First();
	return new ExpandoObject { Letter = mostUsed.Key, Count = mostUsed.Count() };
}

public void RuntimeError(){
	var mostUsed = GetMostUsedCharacterDynamic("testtest");
	Console.WriteLine("{0}: {1}", mostUsed.Letter, mostUsed.Coutn);
}
The code is almost exactly the same, yet one fails as soon as you press F5. Why would I pick the other?

Yeah, great, but this is so trivial that I don't think it's worth worrying about. Using dynamics can save you from writing a lot of extraneous code.

Dietrich
Sep 11, 2001

RICHUNCLEPENNYBAGS posted:

Yeah, great, but this is so trivial that I don't think it's worth worrying about. Using dynamics can save you from writing a lot of extraneous code.

I echo this, somewhat.

Look, work with Javascript a while and you'll realize that it isn't that big of a deal.

That being said, with proper tooling, it takes all of three seconds to create a type with the properties that you want to return when one doesn't exist. alt-enter-enter to freedom with resharper dudes.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

ljw1004 posted:

Allowing anonymous type returns is something that's been discussed in C# and VB language design meetings, but rejected.

...

All my hopes and dreams, dashed away.

RICHUNCLEPENNYBAGS posted:

Yeah, great, but this is so trivial that I don't think it's worth worrying about. Using dynamics can save you from writing a lot of extraneous code.

I don't disagree that dynamics have their place, I just don't think that place is to deal with multiple return values when you could use tuples, anonymous types :smith:, or concrete classes instead.

Dietrich posted:

Look, work with Javascript a while and you'll realize that it isn't that big of a deal.

As I said earlier, I don't mind dynamic typing. I just don't like it mixed in with static typing. My mindset in Javascript is expecting a lack of type safety, so I end up coding more cautiously (checking for existence of methods/properties before invoking them, etc.). In statically typed compiled languages, though, my mindset is expecting the compiler to enforce type rules. Dynamic typing throws those rules out the window at compile time.

Bognar fucked around with this message at 07:09 on Jan 20, 2014

raminasi
Jan 25, 2005

a last drink with no ice

ljw1004 posted:

* The end result is that being able to return tuples or anonymous types is an interesting idea but not quite right. What's needed is a strongly-typed way to return a collection of named values. We haven't yet seen a good enough syntax for this. F# lets you do it but in a kind of mysterious way.
Wait, what? I know F# but I can't think of any way to do this. What's the secret?

Funking Giblet
Jun 28, 2004

Jiglightful!
Returning anonymous types is a really bad idea and breaks type safety.
Each return from a method would need to be checked against every other to ensure they all match, even through deep hierarchies, reflection would be chaos as well.
You would at least need { int:Number, string:Name } as a return type, which isn't so anonymous.

What does Func<int,var> do?

Funking Giblet fucked around with this message at 11:16 on Jan 20, 2014

Adbot
ADBOT LOVES YOU

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice
In my world, you can't define Func<int,var> outside a method anymore than you can declare public property var Fizzulator {get;set;}. Var is just a placeholder that the compiler fills in at compile time. If the type can't be deduced then that's a compilation error. Circular dependencies are a compilation error.

So when you see the VS tooltip for that var-returning method (or disassemble the IL), it is public { int x, string y } MethodName(). By definition, the compiler must be able to determine the shape of var or it won't compile. Having some slight CLR support for this (call it anonymous tuple or whatever) would make it easier for tooling to know what's up and to make it non-brittle by allowing addition of new members in the future, but isn't strictly required.



What holes are there in this scheme? You guys seem to see this as some kind of major problem that I just can't see, so I'm genuinely curious where the idea falls down, subject to the stated limits.



Edit: obviously multiple return points can't return different types in the same method... I think that may be the key unstated assumption missing here. The ultimate shape of the return type could either be required to be identical at all return points, or could be the union of all return points with unspecified members getting default(T).

Simulated fucked around with this message at 20:41 on Jan 20, 2014

  • Locked thread