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.
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]
| does | C# query syntax | F# query { } |
|---|---|---|
| the source | from x in xs | for x in xs do |
| the filter | where p | where (p) |
| the projection | select e | select (e) |
| materialise | .ToList() | |> Seq.toList |
- query { }
- a computation expression whose clauses lower to
seqoperations; the result is aseq<'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
| where | select | result |
|---|---|---|
| x > 5 | x * x | [36; 49; 64; 81; 100] |
| x > 5 | x | [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.
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]
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.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
- Microsoft — F# documentation — query expressions and the
query { }clause reference. - FSharp.Core API reference — the
querybuilder and theSeqoperations its clauses lower to. - Microsoft — .NET documentation — LINQ query syntax and
IQueryable<T>providers on the CLR.
Related in this course
- C1.09 — Computation expressions — the builder machinery
query { }is built on. - C2.07.1 — LINQ operators → Seq operators — the pipeline form of the same query.
- C1.07 — Lists, arrays & the lazy seq —
seqand the transform operators in language depth.