Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Out variables

This is pretty much a terrible language design idea. (Not the "out int x" change, but the whole idea of "out" in an argument list to begin with to indicate values coming out via the arguments.)

The path out of mutable argument bug-hell is not to declare that the values of some argument variables will intentionally get changed by the function in the scope where it is called. It is to make arguments immutable (like they are in the traditional mathy definition of "function"), period, and allow multiple assignment to the function call. What I mean is that, conceptually, ideally, values and state should only flow into the function arguments and only back out of the function call itself.

So for example instead of

> p.GetCoordinates(out int x, out int y);

I think the following would lead to reduced cognitive load and thus fewer bugs:

> int x, int y = p.GetCoordinates;

or even better (color me biased by Erlang/Elixir):

> {x, y} = p.GetCoordinates; # as a pattern match

Instead of a smoothly mentally-traceable flow of values into function arguments and out of function returns, we have this monstrosity of conflating mutable arguments (where values go in and then come back out changed in the scope of the function call) with this "out int x" business which seems to be indicating it intends to "push values upstream", as it were.



> int x, int y = p.GetCoordinates;

Obviously this can be done with the new tuple syntax, but in general I totally agree with everything you've said about 'out'. It is one of the ugliest language features ever, sometimes I wish MS would just say "To hell with backwards compatibility", and throw 'out' on the fire.

    // Function definition
    (int x, int y) GetCoordinates() =>
        (100, 200);

    // Invocation
    var (x, y) = GetCoordinates();


Well, to be charitable, fortunately you can use a style like you propose where values only flow as I've indicated... but you'd still have to contend with OPC (Other People's Code) and if the language allows it, you just know it will turn up somewhere and ruin your day! ;)


Indeed. Although 'out' is rare, it occasionally does ruin my day. So I have created a set of extension methods [1][2] to remove them from my life (because it does physically enrage me whenever I see the need to use it).

[1] https://github.com/louthy/language-ext/blob/master/LanguageE...

[2] https://github.com/louthy/language-ext/blob/daa5a10028e9637b...


> but you'd still have to contend with OPC (Other People's Code) and if the language allows it, you just know it will turn up somewhere and ruin your day! ;)

Yes, like the standard library's Dictionary.TryGetValue()...


you can do

if (dict.ContainsKey(key)) return dict[key] else return null;

the only place where you can't avoid them are TryParse. And there it sort of makes sense - the value is sort of side effect


> if (dict.ContainsKey(key)) return dict[key] else return null;

Which isn't thread-safe. So it's not always a better option.


But Dictionary<TKey,TValue> isn't thread-safe anyway, unless not modified.


Calling TryGetValue will either return a value or not. Calling ContainsKey, followed by an index read can lead to a race condition that means the value isn't there any more.


If I'm reading the source correctly you could still get the bucket index and before you return true and the value, have that value removed from the dictionary. There is no synchronization going on, so concurrent modifications are just as much a problem with TryGetValue as with other approaches. TryGetValue is not atomic either.


Ok, fair enough. My mistake. Glad I don't use the BCL collection types any more and stick to immutable maps, lists, and sets :)


This is correct (and unfortunate).


I feel like out was important for marshalling into C++ code?


Did you read far enough to see the tuple return value syntax?

  var (first, middle, last) = LookupName(id1);
Seems like what you're suggesting, no?


Better. But the id1 argument can still be mutated by LookupName in this scope, no?


Depends.

Based on my hazy memory of C#, if id is a struct/value type it is copied, but if it is a class type, there's nothing preventing the method call from mutating it.

Which is unfortunate, I agree.


> here's nothing preventing the method call from mutating it.

There is, the class could be immutable.

    public class Vector
    {
        public readonly int X;
        public readonly int Y;

        public Vector(int x, int y) 
        {
            X = x;
            Y = y;
        }

        public Vector With(int? X = null, int? Y = null) =>
            new Vector(X ?? this.X, Y ?? this.Y);
    }
I believe record-types are proposed for the next release of C#.


Mutable class with immutable struct wrapper would be better on allocations; then pass in the struct wrapper.


This is correct. One of the things I miss coming from C++ is the ability to pass const references. Then you have the advantage of passing a reference instead of copying an object, and the compiler enforces immutability in the function using the reference.

On a similar note, I also really miss the ability to define const class methods (i.e. instance methods which do not modify the instance on which they are invoked). Although, perhaps get-only accessors fill part of that niche in C#.


This language is called C# because you know... C. out variables are there because people were used to coding like this and now that it's there it would just break things to remove it from the language, so you might as well just make it a bit cleaner.


Fair enough.

But people should stop coding like this. For the love of all that is good and binary, don't use arguments to mutate values in the same scope. Just return them and reassign them. That way, state flow is always in via the arguments and out via the return value(s). Without this discipline, you will have a hodgepodge of "value flows" and that complexity will kill you when debugging. When new values can be spit out both by function returns and function arguments... Hold on, I have to take an aspirin

I know that a lot of OO/procedural languages permit you to do this (unfortunately). Don't!


Yeah , I get you , this coding style is horrible. I personally try to avoid mutating things as much as possible. I wonder how long we will have these old artifacts around before we decide that things need to move to newer langs like F# instead of tacking new features onto the existing lingua franca of .NET.


Been using c# since the day 1, almost never seen someone designing from the ground up with out parameters for the last 10 years. They're not that common. I guess it looks like a favorite feature in the document because it takes the largest part of it, it's not.


Never used a concurrent dictionary or an IDictionary trying to be sanely efficient using TryGetValue instead of ContainsKey followed by a call to the accessor? I can't really believe that you started using c# from the day one and you never encountered an hot code path with a TryGetValue. Of course it gets much worse when you have millions of objects, but even with hundred of thousands it is kind of noticeable. Unless you don't care about caching at all, but that is a complete different kind of dragons'lair...


> out variables are there because people were used to coding like this

This does not invalidate OP's point that it is terrible design. You do not need to take every bad idea in older languages in the name of familiarity.


I believe it is for interoperability with code in these languages, not just familiarity. That said, the BCL also use out parameters in a few places (the TryParse methods) which arguable set a bad precedent.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: