Skip to content

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:

:result :? condition, a, b
# Same as
:result : :? condition, a, b

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:

[1,2,3].{: x ...

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:

for(int i = 0, v = 10; i < 10 ; i++, v--){
}
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:

{
    ...
}.?> x < 5, y < 3 

# Means 'do...while x < 5, y < 3'

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#(... host exit type #) {

}
Host exit types are predefined accepted behavior: * 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:

0
1
2
a
b
c

A better alternative without loops is:

(0:.:2 ++ 'a':.:'c'){: v
  \< v
}