Atomicity of tuple assigment

Tuples in C# have been present since version 4.0 as a library without any language support. C# 7.0 finally introduces proper language integration of tuples. For example, the syntax to create a tuple is simply:

var pair = (1,2);

Deconstructing is also supported:

var (x,y) = pair;

Combining these two features lets us use tuple assignment to swap variables:

(a,b) = (b,a);

Which is almost equivalent to, but more compact and more explicit than, the following:

var temp = a;
a = b;
b = temp;

This behaviour of tuple is akin to atomicity, indeed the left-hand side variables, are only assigned to once all the right-hand side ones have been evaluated. In pseudo syntax of my transactional model we would have:

transaction {
  a = b;
  b = a;
}

where a and b are both semi-mutable variables, hence changes would only be visible after the atomic block. Or in standard C# syntax:

var tempA = a;
var tempB = b;
a = tempB;
b = tempaA;

The tuple assignment syntax to swap two variables simplifies some standard algorithms such as the partitioning in the quick sort algorithm:

    public int Partition(int[] v, int low, int high) {
        var p = v[high];
        var i = low-1;
        for (int k = low; k < high; k++) {
            if (v[k] < p) {                
                i++;
                if (i != k) {
                    (v[i], v[k]) = (v[k], v[i]);
                }
            }
        }
        i++;
        (v[i], v[high]) = (v[high], v[i]);
        return i ;
    }

where the big advantage is that there is no need to use a temporary value or to worry about the order of the various node references are assigned. The downside is readability which decreases as the number of variables increases.

Other algorithms which manipulate linked list can also benefit from this syntax, for example the partitioning of a linked list can be written as:

private ListNode partition(ListNode node, int value) {
if (node == null) return null;
ListNode head = node;
while (node.next != null) {
if (node.next.value < value) {
(node.next, node.next.next, head) = (node.next.next, head, node.next);
} else {
node = node.next;
}
}
return head;
}

where the big advantage is that there is no need to use a temporary value or to worry about the order of the various node references are assigned. The downside is readability which decreases as the number of variables increases.

The atomicity of this tuple assignment in C# is much stronger than, for example, in Python. You might have noticed that in the tuple assignment we first assign a new value to node.next and then to node.next.next. This will give a wrong result in Python, as the lhs are assigned in turn. In C#, the address of the destination variable is evaluated and stored before any assignment is performed.

In terms of atomicity of this assignment operation in case of exceptions, exceptions thrown while evaluating the right-hand side will result in none of the left-hand side value being assigned to. However, if any setter in the left-hand side throws, we can end-up with partially assigned values:

(c.r,c.i) = (1,2);

if the setter of c.i throws an exception, only c.r will have been assigned to, leaving c.i unassigned.

Albeit this assignment is only partially atomic, I find it useful in algorithms where is various variables or properties are swapped around.

Advertisement

Exiting the monad with eval in LINQ

LINQ to object queries are lazy, but this is actually rarely what I need, so in most cases I add a ToList() or a ToArray() in my code before using the result of the query. Since this requires the addition of brackets around the query, the readability of the code suffers and often I skip LINQ and use chained extension methods instead:

var x = (from i in numbers
         where i < 0
         select i*2).ToArray();
var x = numbers.Where(i => i < 0).Select(i => i*2).ToArray();

The same happens with all methods that exit the monad, e.g. First/Last/Single, Any/All/Contains, Aggregate, Average/Min/Max/Sum, Count, etc. So wouldn’t it be nice to have an extra clause in LINQ that would let one apply any of these method to the result of the query?

var x = from i in numbers
        where i < 0
        select i*2
        eval ToArray();

I am using the eval keyword here as it does actually result in the evaluation of the query. In some cases we might want to include a lambda expression:

var x = from i in numbers
        where i < 0 select i*2 eval First(j => j > 8);

which isn’t very pretty but makes it clear that the last bit is not actually part of the query. The alternative would be to add new keywords to LINQ, e.g. first, last, any, etc. But this approach would result in a proliferation of keywords. The advantage of the above approach is that any existing method can be used.

Intermediate approaches might be possible, for example:

var x = from i in numbers
        where i < 0 select i*2 eval First j where j > 8;

where the where keyword could be used whenever the lambda is a predicate? Similarly for Sum/Max/Min/Average

var x = from i in numbers
        where i < 0
        select i*2
        eval Sum j for j*j;

though this one doesn’t look too nice.

Never mind, I have created a fork of Roslyn on Codeplex to try out this idea, … to be continued once the changes has been committed.

[24 hours later] I committed the code in a fork named AddEvalClauseToLinq. It was nice and easy to modify the syntax, but things got trickier when translating the query. I managed to get the example code (see below) to build and execute, but I would have to spend much more time on the compiler to do a less hacky job.

Here is the code I managed to compile:

var ints = new[] { 1, 2, 3 };
Func<int, bool> Trace = i => { Console.Write("<" + i + ">"); return true; };

Console.WriteLine("x = from i in ints where Trace(i) select i*2;");
var x = from i in ints
        where Trace(i)
        select i*2;
Console.WriteLine("type: " + x.GetType().Name);
foreach (var i in x) Console.Write(i + " ");
Console.WriteLine();
foreach (var i in x) Console.Write(i + " ");
Console.WriteLine();
Console.WriteLine();

Console.WriteLine("y = from i in ints where Trace(i) select i*2 eval ToList;");
var y = from i in ints
        where Trace(i)
        select i * 2
        eval ToList();
Console.WriteLine("type: " + y.GetType().Name);
foreach (var i in y) Console.Write(i + " ");
Console.WriteLine();
foreach (var i in y) Console.Write(i + " ");
Console.WriteLine();
Console.WriteLine();

Console.WriteLine("z = from i in ints where Trace(i) select i*2 eval First;");
var z = from i in ints
        where Trace(i)
        select i * 2
        eval First();
Console.WriteLine("type: " + z.GetType().Name);
Console.WriteLine("Value: " + z);
Console.WriteLine();
Console.WriteLine();

Console.WriteLine("s = from i in ints where Trace(i) select i*2 eval Sum(j => j*j);");
var s = from i in ints
        where Trace(i)
        select i * 2
        eval Sum(j => j*j);  
Console.WriteLine("type: " + s.GetType().Name);
Console.WriteLine("Value: " + s);

and here is the console output:

x = from i in ints where Trace(i) select i*2;
type: WhereSelectArrayIterator`2
<1>2 <2>4 <3>6
<1>2 <2>4 <3>6

y = from i in ints where Trace(i) select i*2 eval ToList;
<1><2><3>type: List`1
2 4 6
2 4 6

z = from i in ints where Trace(i) select i*2 eval First;
<1>type: Int32
Value: 2

m = from i in ints where Trace(i) select i*2 eval Sum(t => t*t);
<1><2><3>type: Int32
Value: 56

I think that the console output says it all, types are as expected and only the first query without eval is executed multiple times.

My next Roslyn hack I would love to try would be to get better syntaxic support for tuples, just as in

var x = from (i,j) in listOfTuples
        where i < 0
        select j*2;

or,

var (i,j) = Tuple.Create(1,2);

depending on how hard it turns out to be.

[EDIT] Something very similar has been proposed #3571

Awelon Blue

Thoughts on Programming Experience Design

Joe Duffy's Blog

Adventures in the High-tech Underbelly

Design Matters

Furniture design blog from Design Matters author George Walker

Shavings

A Blog for Woodworkers by Gary Rogowski

randallnatomagan

Woodworking, life and all things between