Pattern matching (PM) is a powerful mechanism for controlling execution flow based on value patterns. It generalizes conditional expressions like if
and switch
in imperative languages, combining multiple conditional checks, value destructuring, and variable binding into a simple structure.
In U, all control flows are based on pattern matching, with conditional expressions being simplified versions that use booleans to return values.
Pattern matching in U is highly versatile, applicable in almost any situation where a decision needs to be made. It always begins with the aperture :?
. The process is straightforward, as it is designed to function similarly to calling U directives, like invoking a function with parameters:
# Check if condition 'a == b' is true
:? a == b, {
# <a> match b
}, { # No 'else' keyword
}, ... # ... optional paramaters
Unlike others languages, control flows have commas to denote choices.
All control flows have an equivalent keyword:
:?
same as::if
:?>
same as::while
Pattern Matching with branches¶
It is the most common use:
:format : 'x'
:num_str : :? format, {
:- 'decimal', :> "1234" # Format == 'decimal'
:-* "0" ++ format ++ "1234" # Else
}
\< num_str
# Prints "0x1234"
Pattern matching must be exhaustive: all cases must be handled.
U can match almost any value and destructuring them to variables:
?: cond, {
# Match pattern and bind value to <X>
:- value, :as: X, ::> # ...
# Match range
:- 0:.:10 # ...
# Match regexp
:- :/[^\n]+/ \...
# Match patterns in a collection
:- .. cursor .., :as: :cursor_var, :> # ...
# Match multiple patterns in collection
:- (head .. tail), :as: (hd, tail), :> # ...
:- :. .. tl # ...
# Match tuples like (1,2), bind 2 to <x>
# Discard first element
:- ( :. , x) ...
:-*
}
Pattern Matching with type check¶
To create mechanisms that accept different value types, we often end up checking for a value type: With type check:
# Shorthand for o.fn is-a MyStruct
:?@MyStruct o.fn {
\< o.fn
}
# Match only types
:?@ a {
:- String ...
:- Number ...
}
# Mixed
:? a {
:-@ String ... # 'a' is a String
:- 3 ... # 'a' == 3
}
Pattern Matching with presence check¶
Pattern Matching is used to check if a value is present in a collection (array, list, range, string):
@Move : [
:left,
:right,
:up,
:down,
]
:input : @Move
...
# Instead of:
:? input == @Move:left ::or input == @Move:right {
// ...
}
# We can write:
:? input, @Move:[left, right], { // ...
# Or simply
:?@Move:[left, right] input { //...
U optimizes such expressions, no array is created.
Ternary Condition Expression¶
U does not have ternary condition expression. Pattern matching already accepts multiple parameters:
Control Flow¶
Pattern matching helps you build loop-free applications. By default, U does not allow loops. If you want to use loops, a condition is mandatory either with:
To use loops, you have to enable them in vgp.
Loop over collections¶
For collections, like arrays, use iterators:
Loop with value condition¶
Loops are special pattern matchings that prevent you from forgetting to set up an exit condition and get stuck in an infinite loop. The exit condition is the condition to break the loop and ensure proper implementation. It helps you think about how the loop terminates before think about its content.
For example, in a C language loop:
You cannot control the loop.But in U:
(:i : 0, :v : 10).?> i < 10, (i++, v--), {: i, v
# U control the loop to assert the exit condition is met
}
To check for exit at the end of a block:
Loop with host condition¶
Computer resources are not infinite. Therefore even infinite end when the host stops a process. Some applications require infinite loop like embedded devices. To allow the use of infinite loop with confidence, U defines the keyword loop
.
Keyword loop
allows you to define infinite loop with a mandatory parameter that shows in the worst case how you expected to be stopped by the host:
loop-crash
: keyword is explicit. We don't know what could happen. If exceptions are used, you can manage kill signal send by host. Otherwise, nothing is guaranteed.
* loop-mem
: like memory > 100M
(ugo: loop-mem),
* loop-timeout
: like < 100s
(ugo: loop-timeout),
* loop-until
condition: like input.pin-12
You can customize this behavior in ugo.
Labeled break & continue¶
break
and continue
control the innermost scope by default. You can also use break
and continue
followed by a label name to refer to an outer loop:
10.times {: i
# 'i' <=> 0, 1, 2, then jump to <num_end>
:? i > 2, :> ::continue num_end
\< i
}
::L num_end
'a':<.<:'z' {: ch
# Break
:? ch > \'c', :> \break
\< ch
}
The above code prints:
A better alternative without loops is: