Skip to content

In U, functions are values: a function can be bound to a name, passed as an argument, returned by a function. Functions allow you to:

  • encapsulate code that can be parameterized with special values,
  • return values when functions are called with or without parameters,
  • call anonymous function, i.e, without binding it to a name,
  • create closures.

U’s unified function syntax with curly braces {} is clear and flexible enough to express all paradigms: from simple function to a complex method with explicit parameters names, types, and labels.

Functions can be defines in two flavors:

  • in-line function: function body fit in one line, statements are separated by semicolon ;.
  • Block function: function body spread in multiple line within a curly brace block {}.

Signatures allow functions to change their body based on input values. Signatures consist of the function’s parameters, return type, and optional function specification. Every signature has a type, consisting of the function’s parameter types and return type. Functions' type are the same as the signature type.

Parameters vs Arguments

Parameters and Arguments have two different meanings that might be confusing if you have no experience in a previous programming language. One way to remember it is that:

  • Parameters are not values and relate to signatures definitions like {: x, y ..., where x, and y are parameters,
  • Arguments are values and relate to function calls like f a, b, where a, and b are arguments.

Another way is to think of a function as a parking lot. In that context:

  • Parameters would be parking slots, both starts with P,
  • Arguments would be Automobiles, both starts with A.

Parameters let you define the properties of values that can fit in your function: type, name, optional or mandatory (like parking slot size in above example). Arguments are values that are passed to function at each call(like cars fill in parking slots). Arguments that don't match parameters raise a type mismatch error.

:add : {: ...parameters;
  ...
}

add arguments
#'arguments' must match 'parameters'

Optional Return

By default, the return value is the last statement one. But you can use ::return keyword, or \^.

:add : {: 
   :result : ...
   \^ result
   # Or
   ::return result
}

Function Definition

Functions can be used before their definition.

In-Line Functions

Short functions can fit in one line. The aperture :> denotes the start of function definition. Function definition ends at the end of the line.

No Signature

The function in the example below is called print_app_name, and just prints the application name Game One. Function without signature are usually replaced by constants values by U compiler.

:print_app_name : :> \< 'Game One'

# Same as 
:print_app_name ::> \< ...

# Call it
print-app-name 
# Prints 'Game One'

The aperture :> denotes function starts, and \< 'Game One' the function body.

With Signature

Functions with a signature take parameter values as input and return values as output. To define signatures, simply starts function definition with :>: which is just: inline function definition :> and a colon :. To separate signature from function body, use :: or parenthesis ():

:walk ::>: speed, goal :: "Robot walking at \:speed miles/h, to \:goal..."

# Same as
:walk ::>( speed, goal ) "..."

# Same as typed version 
:walk ::>: @string <- @int speed, @string goal :: "..."

walk 1, :nowhere

Having the return type close to the function name, enforces the association of the function name and type. It helps you easily detect mismatches when functions are correctly named. For example, if you see:

:hash_int32 :>: @string...
Then something is wrong: either the function name is misleading, or the return type is wrong.

The rule is: :>: optional(RETURN TYPE <-) PARAMETERS :: FUNCTION-BODY.

Block Functions

To define a function with multi-line body, use curly braces {} to group statements in a single block.

For more details about why U is not indent-based, see Braces vs Indentation.

No parameters

:f : { ... }

With Signature

Signatures have the same syntax as for in-line function, except that block function definitions start with {:, which is: block function start { and a colon :. block function definitions ends with a semi-colon ; to be able to define signature on multiple lines.

# Multiple lines
:add : {: x,
        y;
    x += 1
    x + y
}

# Same as this single line
:add : {: x, y; x += 1; x + y }

add 1,2

Anonymous Functions

Functions are values, and as such, can be used directly without being bound to a name. They are name anonymous functions.

\< {: a, b; a + b }. 1, 2
# Prints '3'

# Same as
\< {: a, b; a + b }(1, 2)

# Same as
:fn : {: a, b; a + b }
\< fn(1, 2)

Closures

Closures are special functions that captures variables in a scope:

:add : {: a, b
    # Returning a closure that capture 'a', 'b'
    :>: c :: a + b + c
}

:fc : add 1, 2 # return :>: c :: 3 + c
\< fc 3
# Prints '6' <=> 3 + (c == 3)

They are mainly used to return functions for later use.

Function Calls

Function calls or function application is right-associative. It means that the right side is the argument.

# 'f', 'a', 'b', 'c' are functions
f a b c 
# Means the same as
f( a( b( c() )))
# Call with dot AND space works too
f. a, b, c
(...). 42, "run"

Call with variable function names

Some applications need to be able to execute specific functions that might change in run-time. The most common solution is to use control flow with if x == .... U defines a specific syntax to allow you to call a function with a variable name:

:run ::>: speed :: ...
:stop ::> ...

:a : :run
. a, 1 # Call run
:a : :stop
.(a) # Call stop

See Objects for more details about the dot operator use.

Call with quotes

When function name needs to be computed, use quotes:

:how : "fast"
.("run-\:how") # Call run-fast

Method Calls

Here are simple examples:

object.method(arguments)
object.method ...arguments

Signature

Functions' signature is divided into three sections:

  • Positional mandatory parameters (PMP),
  • Positional optional parameters (POP),
  • Labeled optional parameters (LOP) after slash /.
:complex ::>: a   , speed = 1, / led :: "..."
#          | PMP |    POP    | LOP | function body

Parameters

Parameters can define multiple criteria to accept arguments. The most important thing about parameters is that U functions accept only one parameter !!. When you pass multiple arguments, your pass a sequence to a function:

:f ::>(a,b) ...

f(1,2) # Call 'f' with sequence '(1,2)'
If you have experience with functional languages, you might understand it, but it might appear strange if you have not. This mechanism allows powerful features like polymorphism (accept different value types),and the most notably one is pattern matching on arguments. For example:

:f : {?-
    :- :greet, \< "Hello you!"
    # When passing a sequence with element '2'
    :- .. 2 .., \< "'2' found"
}

f :greet 
# Prints 'Hello you!'

f(1,2,3) 
# Prints "'2' found"

In above example, U compiler easily resolves f(1,2) at the syntax level without overhead by matching the argument sequence (1, 2, 3) with pattern .. 2 ... Therefore, U can match elements 1 <=> .. | 2 <=> 2 | 3 <=> .., and select the branch.

It allows to fastly generate correct and very efficient code.

Parameters Type

Like all type related annotation in U, parameter type starts with @ without colon, as we are not in the execution path that uses aperture @ with colon.

:walk ::>: @int speed, @string goal :: "..."

see Types.

Positional and Labeled Arguments

Positional arguments are the ones used implicitly with function calls. Arguments' position match parameters' position in order:

:f ::>: param1, param2 # ...
f arg1,arg2 
# arg1 <=> param1
# arg2 <=> param2

For better readability, you can precede arguments with names. They are labeled arguments:

:f ::>: name :: # ...
f :name: 'Alice'

Optional Parameters

By default, all parameters are required, but parameters can be made optional by:

  • providing default values,
  • defining a parameter in the optional section.
:move ::>: speed, direction : Forward / light-on, sound-on :: ...

# 'light-on', 'sound-on' are optionals parameters

move 1
move 2, Upward
move 2, Upward, :sound_on: true
move 2, Upward, :light_on: true, :sound_on: true

Variable Length Parameters

As functions receive parameters as sequence, it's easy to call functions with variable length arguments:

:add ::>: ..args  :: ...

add 1    # 'args' <=> [1]
add 1,2  # 'args' <=> [1,2]

Returned Value

All functions return a value. By default, the return value is the last statement one. To specify return value properties, use arrow <-:

:add : {: @int <- @int a, @int b  :: ...
}
See Types.

Ignore Returned Value

:foo ::> (1,2) # Return a sequence

(c, :.) :* foo() # ignore values using `:.`

Pipeline

Function calls are right-associative but you can change this behavior with the pipeline operator |>:

\< square inc 5 # Same as square( inc( 5))
# Prints 36

# With pipeline
\< 5 |> inc |> square
# Prints 36

# With method chaining
\< 5.inc.square
# Prints 36

Pipeline is very useful for array processing where the passed value is always an array. We don't need to check returned types.

Recusive Functions

Recusive functions are the safe and powerful alternative for loops. They allow you to safely repeat function call with new computed value. In U, use keyword ::rec for this purpose.

::rec :factorial : {:
    :? n {
        :- 0, 1
        :-* n * factorial n-1
    }
}

Mutually Recusive Functions

Mutually recursive functions can be defined with the same keyword rec but with curly braces {}:

::rec {
    :even :>: n == 0 ? true, odd n - 1
    :odd  :>: n == 0 ? false, even n - 1
}

Defer

A defer statement defers the execution of a block of statements until the surrounding function returns.

:openFile : {: 
    :f : @File.open 'log.txt', ::defer f.close
    # Same as 
    :f : @File.open 'log.txt', :close: :defer
}

Advanced Functions

Functions can be customized to most needs by changing signature:

{: @string <- @int a, @int b, !> NoMemError, $mem-at 0xFFFE ...

The above example shows an extended signature with:

  • Returned type: string
  • Parameters: a, and b of type int
  • Error result (Sequent): NoMemError
  • Function mapped in memory at address 0xFFFE.

Function specifiers

Work in progress