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 ...
, wherex
, andy
are parameters, - Arguments are values and relate to function calls like
f a, b
, wherea
, andb
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.
Optional Return¶
By default, the return value is the last statement one. But you can use ::return
keyword, or \^
.
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:
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¶
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:
See Objects for more details about the dot operator use.
Call with quotes¶
When function name needs to be computed, use quotes:
Method Calls¶
Here are simple examples:
Signature¶
Functions' signature is divided into three sections:
- Positional mandatory parameters (PMP),
- Positional optional parameters (POP),
- Labeled optional parameters (LOP) after slash
/
.
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:
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.
see Types.
Positional and Labeled Arguments¶
Positional arguments are the ones used implicitly with function calls. Arguments' position match parameters' position in order:
For better readability, you can precede arguments with names. They are labeled arguments:
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:
Returned Value¶
All functions return a value. By default, the return value is the last statement one. To specify return value properties, use arrow <-
:
Ignore Returned Value¶
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.
Mutually Recusive Functions¶
Mutually recursive functions can be defined with the same keyword rec
but with curly braces {}
:
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:
The above example shows an extended signature with:
- Returned type:
string
- Parameters:
a
, andb
of typeint
- Error result (Sequent):
NoMemError
- Function mapped in memory at address
0xFFFE
.
Function specifiers¶
Work in progress