Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

x => … — lambdas, with or without parameter types

x => body · (a, b) => body · (int a, string b) => body · () => body

A lambda is an inline function: a parameter list, `=>`, and a body. Its parameter types come from where it is used, or you write them yourself — `(Order o) => o.Total` — and then they must match that position exactly.

stable7 examples compiled by CIfunctionconstructlinqauthoring

Summary#

A lambda is an inline function — x => x.Total > 10m. Where it is passed decides its parameter types: in orders.Where(o => …) the o is an Order because Where over orders passes one. You may also write the types yourself, as in C#: (Order o) => o.Total. A declared type is then checked against the one the position passes, and where nothing passes one — var twice = (int x) => x * 2; — the declared types are what type the lambda.

Signature#

x => body                    // one parameter, typed by where the lambda is used
(a, b) => body               // several parameters
(int a, string b) => body    // EXPLICITLY typed — all of them, or none
() => body                   // no parameters
x => { statements }          // a block body, wherever the lambda runs (not a query over stored rows)

Description#

Where do a lambda's parameter types come from?#

From the position it is written in — its TARGET. A LINQ clause passes the element (xs.Where(x => …) over a List<int> binds x as int), a Func<T, R> slot passes its Ts, and an index-aware Select((x, i) => …) passes the element and its int position. That is why you rarely need to write a type at all.

enum Pace { Slow, Fast }
entity Delivery { [Required] decimal Weight; Pace Pace; }

decimal Heavy() { return Delivery.Where(d => d.Weight > 20m).Sum(d => d.Weight); }
List<int> Positions(List<int> xs) { return xs.Select((x, i) => x * i).ToList(); }

Can I write the parameter types myself?#

Yes — exactly as in C#. Write each parameter as Type name, and give every parameter a type or none of them:

int FastOnes() {
  return Delivery.Where((Delivery d) => d.Pace == Pace.Fast)
                 .Select((Delivery d) => d.Pace)
                 .Where((Pace p) => p == Pace.Fast)
                 .Count();
}
int Longest(List<string> words) { return words.Select((string w, int i) => w.Length + i).Max() ?? 0; }
int Combine() { Func<int, string, int> f = (int n, string s) => n + s.Length; return f(1, "ab"); }

A typed parameter reads the same as an untyped one inside the body; the type is a statement about it, and the compiler holds you to it.

What if the type I write is not the one the position passes?#

It is a compile error naming both, as in C# (CS1678). A declared type is not a conversion: (long x) => over a List<int> is refused rather than widened, and (int t) => over a sequence of decimal is refused rather than truncated. Nullability counts for a value type — int? is not int.

the lambda declares `x` as `long`, but its target supplies it, so `x` is `int` — a lambda's declared parameter
types must match its target exactly (C#'s CS1678). Write `(int x) => …`, or leave the types off and let the target
supply them: `(x) => …`.

A list that types some parameters and not others — (int x, i) => … — is refused too (C#'s CS0748), with both spellings that work.

Can a lambda stand on its own, with no target?#

Only when its parameters are typed. var twice = (int x) => x * 2; has everything it needs: the declared int types x, and the body's type is what it returns, so twice is a Func<int, int> and twice(3) is an int — C#'s natural type for a lambda. An UNTYPED lambda there has nothing to bind its parameters from and is refused (C#'s CS0815); give it a type, or declare the variable as a Func<…>.

int Twice(int n) {
  var twice = (int x) => x * 2;          // Func<int, int>, from the declared type
  Func<int, int> alsoTwice = x => x * 2; // the same, typed by the variable instead
  return twice(n) + alsoTwice(n);
}

Can a lambda have a block body?#

Yes — x => { …; return y; }, exactly as in C#, wherever the lambda RUNS: a Func/Action held in a variable or passed to a parameter, and a LINQ verb over a list already in memory. The block holds anything a function body can — locals, loops, if, try/catch, calls — and its return answers the lambda. A Func lambda must return a value on every path (C#'s CS1643); an Action lambda's return carries none (CS8030).

[Test]
void Block_Lambdas_Run_Their_Statements() {
  Func<int, int> fold = x => { var t = x * 2; while (t > 10) { t -= 10; } return t; };
  int calls = 0;
  Action note = () => { calls++; };
  var lengths = new List<string> { "kite", "ox", "harbour" }.Select(w => {
    note();
    if (w.Length > 4) { return w.Length * 10; }
    return w.Length;
  }).ToList();
  Assert.Equal(16, fold(4) + fold(9));
  Assert.Equal(76, lengths.Sum());
  Assert.Equal(3, calls);
}

Two places take an expression body only. A lambda that becomes part of a QUERY over stored rows is translated to SQL, and a block has no SQL form — read the rows first (.ToList()) and run the block over the list, or move the statements into a function. And a render expression is evaluated where no call can run, so a lambda invoked there keeps an expression body. A block lambda also cannot wait inside itself for the other side or a durable timer: compute what it waits for before it runs.

Can a lambda change a variable of the code around it?#

Yes, while it runs in the function that declared it — the lambda and the function share the variable, as in C#. Hold the lambda in a local and call it:

[Test]
void A_Lambda_Shares_What_It_Writes() {
  int count = 0;
  Action bump = () => count++;
  bump();
  bump();
  Assert.Equal(2, count);
}

What it may not do is LEAVE that function while it writes such a variable — passed to another function, stored in a field or a list, returned, or not held in a local at all. It would then run where the variable is not, so that is a compile error that says so. Have such a lambda return the new value instead, and assign it where the variable lives.

Does a lambda see a variable that changes after it is made?#

Yes — a lambda captures the VARIABLE, not its value, so it reads what the variable holds when the lambda RUNS, as in C#. That includes a loop variable a for moves on:

[Test]
void A_Lambda_Reads_The_Current_Value() {
  int step = 1;
  Func<int> scaled = () => step * 10;
  step = 5;
  Assert.Equal(50, scaled());

  int total = 0;
  for (int i = 1; i <= 3; i++) {
    Func<int> tenTimes = () => i * 10;
    total += tenTimes();
  }
  Assert.Equal(60, total);
}

The same rule as a lambda that writes: it holds while the lambda runs in the function that declared it. One that is handed on or stored, and reads a variable the function changes afterwards, is refused by name — pass the value to it as a parameter instead. A lambda passed straight to a call (Apply(() => k + 1)) is run by that call before anything around it can change, so it reads the same value either way.

Examples#

decimal Weights(List<decimal> ws) {
  var light = ws.Where(w => w < 5m).Sum();                    // typed by the target
  var heavy = ws.Where((decimal w) => w >= 5m).Sum();         // typed by you — and checked
  return light + heavy;
}

See also#

  • Func<T, R> — a Func<T, R> parameter or field that takes a lambda and is invoked for its value
  • Querying data — LINQ over your data, where most lambdas are written
  • var — var, and what a declaration takes its type from
  • Typed locals — declaring a local with an explicit type

Related

Func<T, R>

A parameter or class field typed `Func<T, R>` takes a lambda and can be invoked for a result, so reusable code can be…

Querying data

How you read data in Osy#. You write C# LINQ; it becomes one SQL statement. The rules that follow from that are the…

var

Declares a local whose type is inferred from its initializer, exactly as in C#. The local is still statically typed —…

Typed locals

Locals can declare an explicit type instead of var; the declared type pins the binding, and every later assignment to…