views:

511

answers:

4

Suppose you are using the ternary operator, or the null coalescing operator, or nested if-else statements to choose assignment to an object. Now suppose that within the conditional statement, you have the evaluation of an expensive or volatile operation, requiring that you put the result into a temporary variable, capturing its state, so that it can be compared, and then potentially assigned.

How would a language, such as C#, for consideration, implement a new logic operator to handle this case? Should it? Are there existing ways to handle this case in C#? Other languages?

Some cases of reducing the verbosity of a ternary or null coalescing operator have been overcome, when we assume that we are looking for direct comparisons, for example. See Unique ways to use the Null Coalescing operator, in particular the discussion around how one can extend the usage of the operator to support String.IsNullOrEmpty(string). Note how Jon Skeet is using the PartialComparer from MiscUtil, to reformat 0s to nulls,

Why is this possibly necessary? Well, take a look at how we write a comparison method for complex objects without any shortcuts (examples from the cited discussions):

public static int Compare( Person p1, Person p2 )
{
    return ( (result = Compare( p1.Age, p2.Age )) != 0 ) ? result
        : ( (result = Compare( p1.Name, p2.Name )) != 0 ) ? result
        : Compare( p1.Salary, p2.Salary );
}

Jon Skeet writes a new comparison to fallback the equality case. This allows the expression to extend by writing a new specific method which returns null, allowing us to use the null coalescing operator:

return PartialComparer.Compare(p1.Age, p2.Age)
    ?? PartialComparer.Compare(p1.Name, p2.Name)
    ?? PartialComparer.Compare(p1.Salary, p2.Salary)
    ?? 0;

The null coalescing operator is more readable because it has two sides, not three. The boolean condition clause is separated into a method, in this case returning null if the expression must be continued.

What would the above expression look like if we could more easily put the condition in-line? Take the expression from PartialComparer.Compare which returns null, and place it in a new ternary expression which allows us to use the evaluation of the left-side expression, with an implicit temporary variable value:

return Compare( p1.Age, p2.Age ) unless value == 0
     : Compare( p1.Name, p2.Name ) unless value == 0
     : Compare( p1.Salary, p2.Salary );

The basic "flow" of an expression would be:

expression A unless boolean B in which case expression C

Rather than being an overloaded comparison operator, I suppose this is more like a short-circuiting inverted ternary operator.

  • Would this type of logic be useful? Currently the null coalescing provides us a way to do this with the conditional expression (value == null).
  • What other expressions would you want to test against? We've heard of (String.IsNullOrEmpty(value)).
  • What would be the best way to express this in the language, in terms of operators, keywords?
A: 

To place one proposed implementation away from a very verbose question, let's run with the unless keyword.

(expression A) unless (boolean B) <magical "in which case" operator> (expression C)

... would be all there is to it.

Boolean expression B would have access to the evaluation of expression A through the keyword value. Expression C could have the unless keyword in its expression, allowing for simple, linear chaining.

Candidates for the <magical "in which case" operator>:

  • :
  • |
  • ?:
  • otherwise keyword
maxwellb
A: 

Usage of any symbols tend to diminish readability for the average developer. Even the ?? operator is not used widely. I, myself, do prefer to develop verbose code, but that I can easily read one year from now.

So a candidate for your :

expression A unless boolean B in which case expression C.

would be

expression A unless boolean B sothen expression C.

Although many people like me would still use:

if (B) {expression C;}
else {expression A;}

This comes in when you are developing a software with a big team, with different backgrounds, each one on the team master of one language, and just user of others.

kurast
However, the use case represented here does not allow one to easily use `if (B) C else A`, because B depends on the evaluation of C or A.
maxwellb
+3  A: 

personally I'd avoid the short circuit from operators and just let the methods chain it:

public static int CompareChain<T>(this int previous, T a, T b)
{
    if (previous != 0)
        return previous;
    return Comparer<T>.Default.Compare(a,b);
}

use like so:

int a = 0, b = 2;
string x = "foo", y = "bar";
return a.Compare(b).CompareChain(x,y);

can be inlined by the JIT so it can perform just as well as short circuiting built into the language without messing about with more complexity.

In response to your asking whether the above 'structure' can apply to more than just comparisons then yes it can, by making the choice of whether to continue or not explict and controllable by the user. This is inherently more complex but, the operation is more flexible so this is unavoidable.

public static T ElseIf<T>(
    this T previous, 
    Func<T,bool> isOK
    Func<T> candidate)
{
    if (previous != null && isOK(previous))
        return previous;
    return candidate();
}

then use like so

Connection bestConnection = server1.GetConnection()
    .ElseIf(IsOk, server2.GetConnection)
    .ElseIf(IsOk, server3.GetConnection)
    .ElseIf(IsOk, () => null);

This is maximum flexibility in that you can alter the IsOk check at any stage and are entirely lazy. For situations where the is OK check is the same in every case you can simplify like so and entirely avoid extensions methods.

public static T ElseIf<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   throw new ArgumentException("none were acceptable");
}

You could do this with linq but this way gives a nice error message and allows this

public static T ElseIf<T>(        
    Func<T,bool> isOK
    params Func<T>[] candidates)
{
    return ElseIf<T>(isOK, (IEnumerable<Func<T>>)candidates);
}

style which leads to nice readable code like so:

var bestConnection = ElseIf(IsOk,
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);

If you want to allow a default value then:

public static T ElseIfOrDefault<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   return default(T);
}

Obviously all the above can very easily be written using lambdas so your specific example would be:

var bestConnection = ElseIfOrDefault(
    c => c != null && !(c.IsBusy || c.IsFull),
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);
ShuggyCoUk
I'd appreciate if you helped me check my use of C# 3 above. I like where you were taking this, but wanted to cover more cases than "Comparison".
maxwellb
Wow. Thanks. I knew that C# was a pretty expressive language, and that there were certainly ways to do this with a handful of methods, and so posed the original question to see if it would add anything by expressing this logic with such an operator. But, you have certainly demonstrated the expressiveness offered by the existing language very completely. The example you provided seems complete and to address any case in which the "logic operation" proposed would apply. The ElseIf generic method, and the ElseIfOrDefault methods you provide seem extremely flexible.
maxwellb
If you enjoy this then I suggest taking a look at functional languages in general (f# being a pleasant way to do this if you are used to .Net)
ShuggyCoUk
A: 

More @ShuggyCoUk: Ah, I see that this might work for more than just comparisons? I haven't used C# 3 and extension methods, but I suppose you can declare, for my previous example, below, a

public delegate bool Validation<T>( T toTest );
public static T Validate<T>( this T leftside, Validation<T> validator )
{
    return validator(leftside) ? leftside : null;
}

Followed by, per Skeet:

Validation<Connection> v = ( Connection c ) => ( c != null && !( c.IsBusy || c. IsFull ) );
Connection bestConnection =
    server1.GetConnection().Validate( v ) ??
    server2.GetConnection().Validate( v ) ??
    server3.GetConnection().Validate( v ) ?? null;

Is this how that would work in C#? Comments appreciated. Thank you.


In response to ShuggyCoUk:

So this is an extension method in C# 3, then? Also, the result here is an int, not an arbitrary expression. Useful for overloading yet another comparison method. Suppose I wanted an expression for choosing the best connection. Ideally, I want something to simplify the following:

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull) ? temp
    :   ( temp = server2.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
        :   ( temp = server3.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
            : null;

Ok, so one could have a method

bool IsOk( Connection c )
{
    return ( c != null && !(c.IsBusy || c.IsFull) );
}

Which would produce:

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) && IsOk( temp ) ? temp
    :   ( temp = server2.GetConnection() ) && IsOk( temp ) ? temp
        :   ( temp = server3.GetConnection() ) && IsOk( temp ) ? temp
            : null;

But how would method chaining for comparisons work, here? I am pondering something which looks like:

Connection bestConnection =
    server1.GetConnection() unless !IsOk(value) otherwise
    server2.GetConnection() unless !IsOk(value) otherwise
    server3.GetConnection() unless !IsOk(value) otherwise null;

I think that there are so far, hoops to jump through, if I want the result of a conditional to be an expression or result of a method which was in the original conditional.

I assume that the object returned by such methods will be expensive to produce, or will change the next time the method is called.

maxwellb
outlined how to use my functional approach with the revised example in edit to my original answer
ShuggyCoUk