Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does the first arrow operator in this Func<T, TReturn> mean?

Given this example code:

enum op
{ 
  add, 
  remove
}

Func<op, int> combo(string head, double tail) => 
  (op op) => 
  op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : Int32.Parse(head) - Convert.ToInt32(tail);

Console.WriteLine(combo("1", 2.5)(op.remove));

Which returns:

-1

What does the first arrow operator mean?
By specification it does not look like an expression body or a lambda operator.
Is there any reference in the C# language specification about this usage?

like image 964
Giulio Caccin Avatar asked Oct 28 '19 20:10

Giulio Caccin


People also ask

What is -> operator in Java?

Basically, the -> separates the parameters (left-side) from the implementation (right side). The general syntax for using lambda expressions is. (Parameters) -> { Body } where the -> separates parameters and lambda expression body.

What is the function of arrow operator?

An Arrow operator in C/C++ allows to access elements in Structures and Unions. It is used with a pointer variable pointing to a structure or union. The arrow operator is formed by using a minus sign, followed by the greater than symbol as shown below.

What does the -> mean in C?

The dot ( . ) operator is used to access a member of a struct, while the arrow operator ( -> ) in C is used to access a member of a struct which is referenced by the pointer in question.

What is the meaning of => in JS?

What It Is. This is an arrow function. Arrow functions are a short syntax, introduced by ECMAscript 6, that can be used similarly to the way you would use function expressions. In other words, you can often use them in place of expressions like function (foo) {...} .


1 Answers

What does the first arrow operator mean?

In newer versions of C#, you are allowed to write:

int M(int x) 
{
  return x + x;
}

as the shorter and clearer:

int M(int x) => x + x;

It is nothing more than a "syntactic sugar" that lets you write a simple method in a shorter and more direct way.

it does not look like an expression body or a lambda operator.

The left => indicates an expression body. The right => indicates a lambda. So it is somewhat confusing in your example because the thing being returned is a lambda, which also uses =>. But do not let that distract you:

Func<int, int> M() => x => x + 1;

is just a short way of writing

Func<int, int> M() 
{
  return x => x + 1;
}

Which in turn is just a short way or writing

static int Anonymous(int x)
{
  return x + 1;
}
Func<int, int> M() 
{
  return Anonymous;
}

If you find code with multiple => operators confusing you can always remove them by desugaring them into the more explicit form. It's just a short form that some people find easier to read.


Can you please write

static Func<op, int> combo(string head, double tail) =>
  (op op) => ...; 

with just one => operator.

Sure. Here are two equivalent ways to write that code.

// Block body, lambda return.
static Func<op, int> combo(string head, double tail)
{
  return (op op) => ...; 
}

// Block body, local function return:
static Func<op, int> combo(string head, double tail)
{
  op MyFunction(op)
  {
     return ...;
  }
  return MyFunction;
}

Sorry, I was not being explicit. What I meant was can you please extract the lambda into its own standalone function, like what one would do in the olden days.

Sure; that is a little more complicated. We will do it in a series of small steps:

We start with this:

Func<op, int> combo(string head, double tail) => 
  (op op) => 
    op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);

Desugar the outer function:

Func<op, int> combo(string head, double tail)
{
  return (op op) => 
    op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
}

Turn the inner function into a helper:

static int Helper(string head, double tail, op op)
{
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
}

Func<op, int> combo(string head, double tail)
{
  return (op op) => Helper(head, tail, op);
}

Now move the helper method to a class:

private class Closure
{
  public int Helper(string head, double tail, op op)
  {
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
  }
}

Func<op, int> combo(string head, double tail)
{
  Closure c = new Closure();
  return (op op) => c.Helper(head, tail, op);
}

Now make head and tail members of closure:

private class Closure
{
  public string head;
  public double tail;
  public int Helper(op op)
  {
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
  }
}

Func<op, int> combo(string head, double tail)
{
  Closure c = new Closure();
  c.head = head;
  c.tail = tail;
  return (op op) => c.Helper(op);
}

And now we can desugar the lambda:

private class Closure
{
  public string head;
  public double tail;
  public int Helper(op op)
  {
    return op == op.add ? Int32.Parse(head) + Convert.ToInt32(tail) : 
    Int32.Parse(head) - Convert.ToInt32(tail);
  }
}

Func<op, int> combo(string head, double tail)
{
  Closure c = new Closure();
  c.head = head;
  c.tail = tail;
  return c.Helper;
}

And we're done.

If you decompile your code using ILDASM or sharplab.io or some such tool you will discover that this is exactly what the compiler generates for your code, except that it generates weird, illegal names for the helpers to ensure that you never accidentally call one of them by mistake.

like image 195
Eric Lippert Avatar answered Nov 19 '22 10:11

Eric Lippert