Functions
A parameterized piece of code is often packaged into a function.
Here’s an example function that adds two numbers:
add: func (a: Int, b: Int) -> Int {
return a + b
}
add(1, 2)
The syntax for function signatures is NAME: func (ARG1, ARG2, ...) -> RETURNTYPE
.
Both the argument definition list and the arrow return type parts are optional, in case
a function takes no argument, or returns nothing. Hence, the following definitions are
all perfectly equivalent:
f: func () -> Void {}
f: func -> Void {}
f: func () {}
f: func {}
Arguments of the same type may be listed in short form: name1, name2, name3: Type
.
Also, the return
keyword is optional - it is only required to exit of the normal
function flow early. Hence, the first example can be rewritten like so:
add: func (a, b: Int) -> Int {
a + b
}
In the absence of a return
keyword in the body of a non-void function, the last
expression will be returned. This works with ifs, matches, etc.
Main
Perhaps the most interesting function at first is the main
function, aka the
entry point of a program. If not defined, one will be implicitly created. For
example, the following is a valid ooc program:
"Hi, world!" println()
If we don’t need to process command-line arguments, we can define the main function simply like this:
main: func {
"Hi, world!" println()
}
Note that any could outside the main function (and outside class definitions, etc.) will get executed before the code in the main function is. This gives a chance for module to run initialization code (e.g. C libraries that need some routine to be called before anything else, to set up stuff).
If we do want command-line arguments, we can do it the C way:
main: func (argc: Int, argv: CString*) {
for (i in 0..argc) {
arg = argv[i]
"Got argument: #{arg toString()}" println()
}
}
Or, we can use an array of strings:
main: func (args: String[]) {
// and so on..
}
Or, we can use an ArrayList of Strings, if more convenient:
import structs/ArrayList
main: func (args: ArrayList<String>) {
// etc.
}
Note that command line arguments might not be relevant for all ooc implementations. One could imagine an ooc implementation that compiles down to JavaScript - in which case, running in a browser, the command line arguments would always be empty.
Suffixes / overloading
Functions can have the same name, but different signatures (argument lists and return type), as long as they have different suffixes. They can be called without suffix, in which case the compiler will infer the right function to call, or explicitly by specifying the suffix by hand:
add: func ~ints (a, b: Int) -> Int { a + b }
add: func ~floats (a, b: Float) -> Float { a + b }
add(1, 2) // calls ~ints variant
add(3.14, 5.0) // calls ~floats variant
add~floats(3, 5) // explicit call
Variable arguments
Special slot in the argument list, can only be at the end, purpose is to accept any number of arguments.
ooc varargs
Store the number of arguments, and the types of each argument. Syntax is as follows:
f: func (args: ...) {
// body
}
Access args count
to know how many arguments were passed.
Use args iterator()
to be able to iterate through the arguments:
printAll: func (things: ...) {
"Printing #{things count} things" println()
iter := things iterator()
while (iter hasNext?()) {
T := iter getNextType()
"The next argument is a #{T name}" println()
match T {
case Int => "%d" printfln(iter next(Int))
case Float => "%.2f" printfln(iter next(Float))
case => "Unsupported type"
}
}
}
More simply, each
can be used on a VarArgs
:
printAll: func (things: ...) {
things each(|thing|
match thing {
case i: Int => "%d" printfln(i)
case f: Float => "%.2f" printfln(f)
case => "<unknown>"
}
)
}
C varargs
Used by writing simply ...
in the argument list, not args: ...
:
f: func (firstArg: Type, ...) {
// body
}
Only accessible through va_start
, va_next
, va_end
. See Variadic
function on Wikipedia.
Useful only to relay a variable number of arguments to an extern C function,
since va_arg
couldn’t work (can’t quote raw C types in ooc).
Example:
vprintf: extern (s: CString, ...)
printf: func (s: String, ...) -> This {
list: VaList
va_start(list, this)
vprintf(s toCString(), list)
va_end(list)
}
Extern functions
To call a function defined elsewhere, for example in a C library, its prototype needs to be defined
exit: extern func (Int)
Lazy way: type-only args, thorough way: variable-decl-args.
exit: extern func (exitCode: Int)
By-ref parameters
Instead of having to dereference each time the parameter is accessed, just declare it as a reference type:
increment: func (a: Int*) { a@ += 1 }
// vs
increment: func (a: Int@) { a += 1 }
Saves typing, saves error, clearer code. Can still access the address via argument&
.
Default parameters
Default values for parameters can be specified using :=
greet: func (name := "John Doe") {
"Hello, #{name}!" println()
}
greet() // prints: "Hello, John Doe!"
greet("Rita") // prints: "Hello, Rita!"
A function can have any number of default parameters, but they should all be at the end of the parameter list, never interleaved with non-default parameters.
Closures
A very concise way to pass a function as an argument. each
is a typical
example:
// definition
List: class <T> {
each: func (f: Func (T)) { /* ... */ }
}
// usage
list := List<Int> new()
list each(|elem|
// do something with elem, of type Int
)
Also works with several parameters:
// definition
Map: class <K, V> {
each: func (f: Func (K, V)) { /* ... */ }
}
// usage
map := Map<String, Horse> new()
map each(|key, value|
// key is of type String, value is of type Horse
)
Argument types are inferred, hence, the code is very short.