– Typee Documentation –
3 – Typee Language Description
3.10 – Functions
Functions, and methods which are functions declared within classes to be applied to the instances of these classes, are parts of code that are factorized end then called at multiple places in a more global software code.
Typee specifies functions (and methods) the same way as most OOP (Object Oriented Programming) languages do. Functions can be declared, defined and called. They may be templated with template arguments. They may get arguments. Finally, they are typed; the type of a function is the type of the value it returns back to its caller once it has completed. Among all valid types is type none
, also writable None
, which means that the function just runs some code but do not return any value.
Typee specifies also unnamed
functions. These are named lambda
in Java and Python.
Finally, functions (and methods as well) may be overloaded and overridden. We will explain what does this mean, later in this section.
The overall formal EBNF specification of functions (and methods) rules in Typee are:
<function call statement> ::= <function call> ';' <function call> ::= <dotted name> [<template args>] '(' <function call args> ')' <function declaration> ::= <forward> <function decl> ';' <function definition> ::= <function decl> ['exclude' <languages>] <statements block> <access protection qualifier> ::= 'hidden' | 'local' | 'private' 'protected' | 'public' <call operator> ::= '(' ')' <enclosed types list> ::= '(' <types list> ')' <forward> ::= 'forward' | 'fwd' <function args declaration> ::= '(' [<typed args list>] ')' <function call args> ::= [ <expression> (',' <expression>)* ] <function decl> ::= [<access protection qualifier>] ['final'] <type> <identifier> [<template def>] <function args declaration> <language> ::= 'android' | 'cpp' | 'java' | 'cs' | 'csharp' | 'py' | 'python' <languages> ::= <language> ( ',' <language> )* <return statement> ::= ( 'ret' | 'return' ) <expr list> <expr list> ::= <expression> ( ',' <expression> )* <typed args list> ::= <type> <var or func identifier> ( ',' <type> <var or func identifier> )* [ ',' [ 'const' ] '...' <identifer> ] <unnamed> ::= 'unnamed' | 'lambda' <unnamed func> ::= <unnamed> [<type>] <function args declaration> <statements block> <var or func identifier> ::= <identifier> [<enclosed types list>]
<spaced template args> ::= ' <' <template args'> '>' <spaced template def> ::= ' <' <template def'> '>' <template args> ::= '<' <template args'> '>' <template args'> ::= [<expression> ( ',' <expression> )*] <template def> ::= '<' <template def'> '>' <template def'> ::= (<identifier> | 'const' <template def const name>) ( ',' (<identifier> | 'const' <template def const name>) )* <template def const name> ::= <scalar type or dotted name> <identifier> [ '=' <expression> ]
<for comprehension> ::= 'for' '(' <target list> 'in' <or test> <iter comprehension> ')' <iter comprehension> ::= [ <for comprehension> | <if comprehension> ] <if comprehension> ::= 'if' '(' (<or test> | <unnamed func>) ')' <iter comprehension> <targets list> ::= <target> ( ',' <target> )* <target> ::= <dotted name> (<subscription or slicing>)* <dotted name> ::= <identifier> ( '.' <identifier> )*
3.10.1 Function Definition
The definition of a function specifies:
- the access protection level that is associated with this function, when the function is defined at the highest level of statements-blocks nesting of the module it is defined in;
- the overridding possibility or not on this function;
- the type of the value this function returns back to its caller;
- the identifier of this function;
- a list of template arguments when this concept is used;
- the list of the typed arguments that have to be passed to this function when it is called;
- a block of statements which contains all the statements that this function is to compute.
A function is designed by its signature rather than by its identifier which is part of this signature. At call-time, the function body that is computed is the one associated with the function signature that matches the call signature.
Those elements are constituting the signature of a function:
- the type of the value it returns to its caller;
- the identifier of the function;
- the template arguments that may be declared and applied to it – if present;
- the whole ordered list of the arguments types that are declared in its definition.
The function body, the access protection qualifier and the overridding possibility are not part of a function signature.
3.10.1.1 Access Protection
The access protection level of a function is either set locally for it, with protection keywords, or set at a global level, as seen before, between two colons. When set locally at the definition of the function, the specified visibilty level takes precedence over any global level set before in the same source code file. Access protection levels may only be set at the highest level of blocks nesting within a module. Typee translators will set warnings when any vivibility qualifier will appear in nested blocks of instructions within a module (i.e. within a Typee source code file).
As a reminder, visibility qualifiers in Typee are:
<access protection qualifier> ::= 'hidden' | 'local' | 'private' 'protected' | 'public'
3.10.1.2 Overridding
Overridding possibility is the default in Typee. As will be explained later in this documentation, overridding is a way to modify the definition of a function (with same signature) within a specified area of code. Typee accepts the definition of functions (and methods) that should not be overridden. The keyword for indicating this is the same as in Java: final
.
3.10.1.3 Returned value
Functions may return values to their callers. The type of the returned value has to be specified and is considered as belonging to the signature of the function.
Example:
const int32 my_sum( const int32 a, const int32 b ){ return a + b; }
The returned types may be any kind of type, and even containers, instances of classes, etc. Meanwhile, functions may also return no value. In this case, the returned type has to be specified as none
:
none display(){ // ... }
3.10.1.4 Function identifier
The identifier of a function is set as any other identifier in Typee: it has to start with a letter or an underscore. It may contain then any number of letters, decimal digits and underscores. No assumption is made in Typee, while it is the case in Python for instance, about the number of starting or ending underscores. Programmers wishing to translate their Typee source codes to many different programming languages including Python should nevertheless be careful about the naming of the identifiers they declare and define. In such a case, avoid identifiers prefixed with two underscores as well as identifiers prefixed and suffixed with two underscores also. These are special conventions of naming in Python.
Furthermore, avoid any function names which would be the same as libraries functions. Typee translators will do their best to avoid colliding naming, but it might be that this will not be possible at some times. For sure, colliding names will finally be detected at compile- or at interpretation- time of the translated source code but programmers should do their best to avoid such troubles after translation-time.
Notice that a function identifier is part of the function signature. Functions are called according to their signatures, not according to their sole identifiers.
3.10.1.5 Templates
Templates will be fully explained later in this documentation. Let’s just talk here about their syntax when defining a function. Template arguments are part of the signature of the function. They are declared at its definition time. They are enclosed between angle brackets: <
and >
. They consist in a list of identifiers which will only be visible in the body of the function (i.e. its block of statements), separated by commas. Any single identifier within the list is the local identifier of a type which will be inferred at call-time of the function. Other identifiers, when preceded by a type identifier (either a built-in one or an already defined one),are identifiers of constant values that will be locally visible in the body of the function, taht may get a default value and that will be inferred at call-time of the function.
The formal EBNF specification of Typee related grammar syntax is:
<template def> ::= '<' <template def'> '>' <template def'> ::= (<identifier> | 'const' <template def const name>) ( ',' (<identifier> | 'const' <template def const name>) )* <template def const name> ::= <scalar type or dotted name> <identifier> [ '=' <expression> ]
Example:
const ScalarT my_sum<ScalarT>( const ScalarT a, const ScalarT b ){ return a + b; } print( my_sum<int32>(1, 2), my_sum<float64>(2.718_281_828_459_045, 6.022_140_857) ); // prints: 3 8.740422685459045
3.10.1.6 Function arguments
Functions get passed arguments. The block of statements of the function processes these arguments or uses these arguments while running through it. The ordered list of the types of the arguments is part of the signature of the function. At definition- and at declaration- time, each argument is specified by its identifier, which has to be unique in the list of arguments, and by its type. Arguments types may be preceded by keyword const
. In this case, the related arguments are considered to be not mutable outside of the function. This means that the values of these arguments may be modified within the function body, but that they will just be modified locally while their initial value at call-time will not be modified once control will have been returned back to the caller of the function.
A special case is the very last argument of a function which may be a variable argument. It is preceded by the ellipsis keyword ...
. This special argument is to be considered as a list of variable length. Here again, keyword const
may precede the ellipsis, in which all contained arguments in this variable-length list are not mutable – i.e. their values will not be changed outside the function body. This variable argument is provided for convenience of programming, helping not to have to code too many different signatures for a same function. But this comes with a big disadvantage: type checking will be broken there at translation time.
Example:
none my_print( const str hdr, const ... args ){ str final_txt = hdr; for( arg in args ) final_txt += ' ' + arg; print( final_txt ); }
Another special case here is when a function is declared as an argument of a function. The signature of the argument function is then to be declared.
Example:
bool is_greater<ItemType>( const ItemType item1, const ItemType item2, int32 comp_func(const ItemType, const ItemType) ) { return true if comp_func(item1, item2) > 0 else false; } int32 gt( const int32 itm1, const int32 itm2 ){ return itm1 - itm2; } print( is_greater<int32>(3, 1, gt), is_greater<int32>( 2, 2, gt) ) // prints: true false
3.10.1.7 Arguments types, part of the function signature
It may be that within the many signatures of a function or a method, some signatures share the same number of arguments but with overlapping accepted types due to the any type (the one that is specified with keyword ?
.
In that case, when some types have been specifically specified in some of the signatures and an argument of those types is passed at call time, the called function is the one with the specified type. The signature with the ?
any type specification will then be called for all other types than the ones specified otherwise in other signatures of the same function.
Example:
/* (1) */ fwd none some_func( ? arg1, uint32 arg2 ); /* (2) */ fwd none some_func( ? in (uint32,int32) arg1, uint32 arg2 ); /* (3) */ fwd none some_func( list arg1, uint32 arg2 ); some_func( 16, 10 ); // calls signature (2), 16 and 10 being cast to uint32 some_func( [1,2,3], 11 ); // calls signature (3) some_func( 45.678, 12 ); // calls signature (1) some_func( 'text', 13 ); // calls signature (1)
3.10.1.8 Function body
The body of a function is the statements block that is associated with it. It is not part of its signature. The formal specification of statements blocks is described in the dedicated section of this Typee documentation.
3.10.2 Function Declaration
In Typee, functions may only be declared in a forward statement. The only need to declare a function is when it is to be defined in a module after it is called in this same module. Unnamed functions have not to be declared also. They are always defined at the place they are called.
The formal EBNF sepcification of a function declaration is then:
<function declaration> ::= <forward> <function decl> ';' <forward> ::= 'forward' | 'fwd'
Since the funcion is only declared there, no statements block (i.e. function body) is allowed after the declaration of the signature of the function. This declaration bieng a statement, it has to be ended with a semi-colon. Very simple.
Such declarations help Typee translators to check types and functions signatures when they are called with no delay (this is a not-lazy evaluation or checking of types and signatures).
3.10.3 Function Call
Functions are called with values or references as arguments. The types of the arguments define part of the signature of the function to be called. The other part of the signature is the type of its returned value. In case of possible ambiguities about returned type, Typee translators will set an explicit warning or even erro acording to the contextr. This is discussed in a next paragraph.
Let’s first describe the formal EBNF specification of functions calls:
<function call> ::= <identifier> [<template args>] '(' <function call args> ')' <function call args> ::= [ <expression> (',' <expression>)* ]
The identifier is the identifier of the function.
It may be followed by template arguments if declared or defined so. Notice: templates arguments are enclosed within angle brackets <
and >
and are lists of types or constant values – more about templates later in this documentation.
Next are the values, variables or references passed as arguments to the function at call-time. These arguments are seperated by commas and are enclosed between parenthesis: (
and )
. Their number and the ordered list of their types are part of the signature of the called function. Should any ambiguity be found at translation time, Typee translators will set an explicit error.
Finally, to fully evaluate the signature of the called function, its returned type is evaluated at translation time.
A function that returns no value can only be called in a function call statement
:
<function call statement> ::= <function call> ';'
When a no-value returning function will be called in any arithmetic expression, assignment statement and conditional expression, Typee translators will set a type error.
The type of the value returned by a called function will be inferred at translation time, according to the context of the call. Most of the time, this will be straightforward to evaluate. For instance, when assigning a variable with the result of a function the type of the assigned variable will be used for the type inferrence. Or, when calling a function in an arithmetic or conditional expression, the inferred type for returned type of the called function will be the type of the other terms in the evaluated expression.
Meanwhile, it may happen that according to a same function identifier and types of arguments list, a couple of returned types could be inferred for the called function – see simple example below. Typee translators will then:
- either set a warning if all the possibly inferred returned types are all compatible together and with the expected type in the expression or assignment (e.g. integers and floats), informing that the largest type will be used:
- floats first, 64-bits before 32-bits;
- integers then, in this order: 64-, 32-, 16- and then 8- bits; signed types before unsigned types if any of the other types inferred in the expression cannot be clearly inferred as unsigned;
- or set an error if possibly inferred returned types are not compatible with the expectedtype.
Programmers are informed that they should avoid as much as possible any ambiguity with signatures of called functions. Programming languages such as C++ or Java do not allow functions with same identifier to return different types of values. Typee allows this. It is simply processed at translation time when translating Typee code to programming languages that do not allow it. Then, programmers have to be careful when using this Typee goody in the context of its further translation to such not permissive programming languages.
3.10.5 Unnamed Functions
Unnamed functions are functions which are used in-place. They are functions that are defined at the exact place where they are called, so they do not need to get identifiers. These functions are called lambda functions in other programming languages, e.g. Python or Java. They are used to define callbacks in Javascript also.
Their formal EBNF specification is:
<unnamed func> ::= <unnamed> [<TYPE>] <function args declaration> <statements block> <function args declaration> ::= '(' [<typed args list>] ')' <typed args list> ::= <type> <identifier> ( ',' <type> <identifier> )* <unnamed> ::= 'unnamed' | 'lambda'
In Typee, these functions are called unnamed
just because they get no identifier. As programmers are used ot use keyword lambda
in other programming languages, this keyword is also accepted wioth Typee but programmers are strongly encouraged to use unnamed
instead when programming in Typee.
Usage of unnamed functions will be fully explained later in this documentation.
Example – a little bit complex, but experienced programmers will understand it:
forward TContainer qsort<TContainer>( bool f_comp<T>(const T a, const T b), TContainer container ); list<uint8> my_list = [54, 3, 1, 2, 0]; my_list = qsort<list<uint8> ( unnamed<uint8> bool(const uint8 a, const uint8 b){ return a < b; }, my_list ); // results in my_list <-- [0, 1, 2, 3, 4, 5]
3.10.6 Function Overloading
We shortly discuss this concept here. There is no specific rule for it in Typee grammar.
Overloading has to do with functions (and methods) signatures. A function (or a method) is said to be overloaded as soon as it gets many different signatures with its same identifier.
We stress here that point, generic to any OOP language, because Typee gets a specificity about it. The returned type of functions and methods is aprt of their signature. This is not the case in most programming languages.
Example:
fwd int64 sum( list<int32> nums ); fwd float32 sum( list<float32> nums ); fwd float64 sum( list nums );
3.10.7 Function Overriding
We shortly discuss this concept here. There is no specific rule for it in Typee grammar.
Overridding is the rewriting of the body (i.e. statements block) of a method while not changing its signature. This is classical in Objetc Oriented Programming, while redefining the purpose of a method in an inheriting class.
Example:
class A{ static none func( const str s ){ print( "in A.func(", s, ")" ); } } class B: A { static none func( const str s ){ print( "in B.func(", s, ")" ); // overridding } }
3.10.8 Typee built-in function print()
Typee defines one and only one built-in function:
none print( … args )
Prints on console the arguments args
which can be of a variable number and of variable types. Nevertheless, every passed arguments must be cast-able to string. This concept is explained in other sub-section of this typee documentation.
The printing of strings can be formatted as explained in sub-section dedicated to strings in this documentation of Typee.
Arguments are separated by spaces when printed on the console. A newline is automatically added at end of the printed text.
Example:
print( 1, 2, 345, "six", "-seven" ); /** prints: 1 2 345 six -seven **/
none print_sep( const ? in(char,str,char16,str16) sep, … args )
Prints on console the arguments args
which can be of a variable number and of variable types. Nevertheless, every passed arguments must be cast-able to string. This concept is explained in other sub-section of this typee documentation.
The printing of strings can be formatted as explained in sub-section dedicated to strings in this documentation of Typee.
Arguments are separated by the specified sep
when printed on the console. A newline is automatically added at end of the printed text.
Example:
print_sep( " / ", 1, 2, 345, "six", "-seven" ); /** prints: 1 / 2 / 345 / six / -seven **/
none print_end( const ? in(char,str,char16,str16) end, … args )
Prints on console the arguments args
which can be of a variable number and of variable types. Nevertheless, every passed arguments must be cast-able to string. This concept is explained in other sub-section of this typee documentation.
The printing of strings can be formatted as explained in sub-section dedicated to strings in this documentation of Typee.
Arguments are separated by spaces when printed on console. Pattern end
is added at end of the printed text on the console.
Example:
print_end( '#', 1, 2, 345, "six", "-seven" ); print( "appended text" ); print( "final" ); /** prints: 1 2 345 six -seven#appended text final **/
none print_sep_end( const ? in(char,str,char16,str16) sep, const ? in(char,str,char16,str16) end, … args )
Prints on console the arguments args
which can be of a variable number and of variable types. Nevertheless, every passed arguments must be cast-able to string. This concept is explained in other sub-section of this typee documentation.
The printing of strings can be formatted as explained in sub-section dedicated to strings in this documentation of Typee.
Arguments are separated by pattern sep
when printed on console. Pattern end
is added at end of the printed text on the console.
Example:
print_sep_end( ' / ', '#', 1, 2, 345, "six", "-seven" ); print( "appended text" ); print( "final" ); /** prints: 1 / 2 / 345 / six / -seven#appended text final **/
Next section formerly explains operators definitions and overrriding.