Patterns - IS4Code/Sona GitHub Wiki
Patterns are expression-like constructs that are used to test whether a value has a particular shape, and, optionally, extract any relevant variable parts. They are used in all constructs that store values in variables, such as let
/var
statements, function
parameters, primary constructors, and case
rules.
Patterns may be considered "partial" or "total". A total pattern always succeeds and thus is useful only for extracting variables from structured values, while a partial pattern may reject certain inputs. Partial patterns are allowed only in case
rules, since in the case of an unsuccessful match, the control flow proceeds to the next case
or else
.
The syntax of patterns parallels that of expressions, using the same constructs and operators, "undoing" the expression that would form the matched value, into its individual components.
Any numerical constant, literal string, or other built-in value in itself forms a pattern, testing whether the matched value equals to that constant. In addition, a qualified or unqualified name may also be used to test against a named constant or enum
member.
A name starting with a lower-case letter indicates a variable, which is bound to the value matched by the pattern. Like in all other places, it can be followed by as
and a type, asserting the type of the variable.
Named patterns mimic function calls, referring to a pre-existing discriminator, either a union case or a case function, to match the value and extract its components.
Patterns may form logical conjunctions or disjunctions using and
and or
. Similarly to expressions, both operators are mutually nonassociative, requiring parentheses to indicate precedence.
- Patterns separated by
or
indicate multiple unrelated alternative shapes of the value. They can differ in structure, but all alternatives must export the exact same set of variables. - Patterns separated by
and
indicate progressive refinement of the value. Unless grouped in parentheses, the type of the preceding patterns in conjunction restricts the type of the following patterns; for exampleis<int> and NonZero()
references theNonZero
named pattern expectingint
.
A pattern preceded by is
permits optional narrow
operation to transform the type of the input to the type of the output (i.e. the argument to is
). The type is generally inferred from context, but may be indicated using <
…>
, such as is<int> i
.
Construction patterns share the same syntax with tuples, arrays, and non-anonymous records:
- In a tuple or array with two or more elements, some of them may be omitted completely, e.g.
(, x)
/[, x]
extract only the second element in the tuple or array, respectively. - In a record, omitting the values for at most all but one field, such as
{ x = x, y, z }
, is equivalent to using a variable pattern of the same name as the field (e.g.{ x = x, y = y, z = z }
).
A pattern in the form with { field = pattern, … }
(similar to a record pattern) is a member-testing pattern, matching the values of the object's members with the individual patterns. Similarly to record patterns, omitted values result in variables of the same name as the member.
A pattern starting with a relational operator ==
, !=
, <
, >
, <=
, >=
is a relational pattern, comparing the matched value to the value obtained by evaluating the expression to the right of the operator. Even though the expression is not required to be constant; due to F#'s rules, it must still be a syntactically valid pattern, so it is best to avoid using complex expressions there.
Field assignments in record and member-testing patterns also allow a shorthand syntax when a relational operator is used instead of the =
, such as with { Length > 0 }
.
A pattern in the form of /regular expression/options-options
indicates a regular expression, a special kind of pattern for matching strings. The syntax follows closely that of the Regex
class in .NET, however there are several differences:
-
Any literal use of the characters
/
,(
,[
,{
,)
,]
,}
,#
,"
,'
, must be escaped via\
, even if they are used in a position where escaping would be unnecessary for normal regular expressions (comments and directives have priority over regular expressions). Additionally, the pattern must not span multiple lines ‒\r
or\n
should be used instead. - The regular expression is matched as culture-invariant, with the explicit capture option (
n
) set. - The terminating
/
character may be followed by option overrides, similarly to the(?imnsx-imnsx)
construct in .NET. - Within the pattern, a special token
(?{
is recognized, indicating an extracted capture group pattern, lasting until the terminating}
. This pattern receives the sub-match value of the regular expression that follows:- These sub-patterns are assigned numeric names in the order they are written in the regular expression, starting from
1
, i.e. the first such occurrence is transformed into(?<1>
, the second into(?<2>
, and so on. It is possible to refer to them using back-references. - Using additional capture groups than those indicated by the
(?{
syntax is prohibited, however it is possible to write additional captures for a group defined elsewhere, using(?<1>
,(?<2>
, etc. - The pattern receiving the value of the capture group is contextually inferred to be either
string
,ReadOnlyMemory<char>
, or an option type thereof. This permits retrieving back the position of the capture in the input string viaMemoryMarshal.TryGetString
, or checking whether it was matched at all (due to alternatives).
- These sub-patterns are assigned numeric names in the order they are written in the regular expression, starting from
The regular expression instance formed by the pattern is cached and reused for each occurrence of that same pattern throughout the compilation.