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.

Constant patterns

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.

Variable patterns

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

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.

Logic patterns

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 example is<int> and NonZero() references the NonZero named pattern expecting int.

Type-testing patterns

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

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 }).

Member-testing patterns

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.

Relational patterns

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 }.

Regular expressions

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 via MemoryMarshal.TryGetString, or checking whether it was matched at all (due to alternatives).

The regular expression instance formed by the pattern is cached and reused for each occurrence of that same pattern throughout the compilation.

⚠️ **GitHub.com Fallback** ⚠️