jonnifyfsharp

Dive C2.07.3

Query expressions

The LINQ query syntax, in F# — a computation expression that compiles to the same seq operations.

C# has two faces for LINQ: the method chain, and the query syntax — from x in xs where … select …, which reads like a small SQL. F# answers the second with query { }, a block of clauses with from spelled for … in, and where and select kept as they are. Under it, query { } is a computation expression — the same machinery as async and seq — so the clauses lower to ordinary sequence operations. Most F# stays with the |> pipeline; this form earns its place for join, groupJoin, and IQueryable providers that translate the query to SQL.

C2.07.3 · part 1

from, where, select — almost word for word

The query-syntax keywords map nearly one to one. The only adjustment is the source clause: C#'s from x in xs is F#'s for x in xs do; where and select are unchanged. The result is a seq, forced here with Seq.toList. The echoes were produced — the F# by dotnet fsi, the C# by dotnet-script.

C# — LINQ query syntax

var result =
    (from x in Enumerable.Range(1, 10)
     where x > 5
     select x * x).ToList();          // [36, 49, 64, 81, 100]

F# — query { } expression

let result =
    query { for x in [1..10] do
            where (x > 5)
            select (x * x) } |> Seq.toList
// val result : int list = [36; 49; 64; 81; 100]
Query keyword · C# · F# query { }
doesC# query syntaxF# query { }
the sourcefrom x in xsfor x in xs do
the filterwhere pwhere (p)
the projectionselect eselect (e)
materialise.ToList()|> Seq.toList
query { }
a computation expression whose clauses lower to seq operations; the result is a seq<'T>.
for … in
the F# spelling of the query source clause — the only keyword that differs from C#.
where / select
kept verbatim from the LINQ query syntax; in F# the clause body is parenthesised.

Build a query { } block

Toggle the clauses over the fixed range [1..10]. The block on the left rewrites to match, and the readout shows the result — the same value dotnet fsi returns. A query { } needs a select, so that clause stays; the where is optional, and select toggles between the element and its square.

query { for x in [1..10] do … } · clauses → result

[1..10] where (x > 5) select (x * x) [36; 49; 64; 81; 100]
query { for x in [1..10] do where (x > 5); select (x * x) } |> Seq.toList = [36; 49; 64; 81; 100]
Without JavaScript: the four clause combinations over [1..10]
whereselectresult
x > 5x * x[36; 49; 64; 81; 100]
x > 5x[6; 7; 8; 9; 10]
—x * x[1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
—x[1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

The block reads as the query a C# developer already writes, with one keyword changed. It is the LINQ query syntax with F# spelling, lowered to the same sequence operations underneath.

C2.07.3 · part 2

When query { } earns its place

For a filter-and-map, the |> pipeline is the F# default — it is shorter and composes with the rest of the language. query { } is the better fit in two cases. First, when a query reads more clearly as clauses: a join of two sources, or a groupJoin, is awkward as a pipeline and natural as query clauses. Second, when the source is an IQueryable — an Entity Framework table, say — where the provider reads the query expression and translates it to SQL rather than running it in memory.

// query { } is a computation expression — the same machinery as seq { } and async { }.
// The builder value is `query`; the clauses (for/where/select/join/groupBy/sortBy …)
// are its custom operations, lowered to Seq / IQueryable calls.
query { for x in [1..10] do
        where (x > 5)
        select (x * x) } |> Seq.toList
// val it : int list = [36; 49; 64; 81; 100]
A query { } over an in-memory list and the equivalent |> pipeline produce the same result; the choice is readability and the provider. Over an IQueryable, only query { } hands the provider a query it can translate — a pipeline of Seq. functions would pull every row into memory first.
C2.07.3 · part 3

Two syntaxes, one machinery

The pipeline and the query expression are the two F# spellings of a LINQ query, matching C#'s method syntax and query syntax. The pipeline is the everyday choice; query { } is a computation expression that reads as clauses and can drive an IQueryable provider. A C# developer already knows both shapes — this dive supplies the F# names.

C# habit

LINQ query syntax — from x in xs where … select … — reading like a small SQL over a source.

F# form

query { for x in xs do where … select … } — a computation expression lowering to seq operations.

Where this leads

That closes the LINQ-to-seq module: the operator map, the deferred execution both languages share, and the two syntaxes. query { } is one of many computation expressions; the language chapter takes the machinery itself — the builder, the custom operations, and how seq, async, and query all sit on it.

References

Sources

Related in this course