-
-
Notifications
You must be signed in to change notification settings - Fork 9
Functions
Functions are blocks of code that take zero or more parameters and optionally return a value.
There are multiple syntaxes for declaring functions, each varying in style preferences and specificity.
All function declarations are a combination of the following:
-
func
keyword - function name
- parameters (sometimes omitted)
- return type (sometimes omitted)
- block or expression
You can declare a function using any of the following syntax styles.
func myFunction {}
func myFunction ReturnType {}
func myFunction(arg_name ArgType, arg_name ArgType) {}
func myFunction(arg_name ArgType, arg_name ArgType) ReturnType {}
func myFunction ReturnType = return_value
func myFunction(arg_name ArgType, arg_name ArgType) ReturnType = return_value
myFunction :: func {}
myFunction :: func ReturnType {}
myFunction :: func (arg_name ArgType, arg_name ArgType) {}
myFunction :: func (arg_name ArgType, arg_name ArgType) ReturnType {}
myFunction :: func ReturnType = return_value
myFunction :: func (arg_name ArgType, arg_name ArgType) ReturnType = return_value
myFunction :: func {}
myFunction :: func : ReturnType {}
myFunction :: func (arg_name : ArgType, arg_name : ArgType) {}
myFunction :: func (arg_name : ArgType, arg_name : ArgType) : ReturnType {}
myFunction :: func : ReturnType = return_value
myFunction :: func (arg_name : ArgType, arg_name : ArgType) : ReturnType = return_value
Function parameters are always given as a list of argument names and argument types all surrounded by parentheses. When there are zero parameters, the parameter list can be omitted, or an empty parameter list ()
can be used.
(name String)
(name String, age int)
(firstname, lastname String)
(name: String)
(name: String, age: int)
(firstname, lastname: String)
If multiple parameters in a row have the same argument type, then the shared argument type doesn't have to be specified multiple times.
For example
func setPosition(x float, y float, z float) void { /* ... */ }
can be abbreviated as
func setPosition(x, y, z float) void { /* ... */ }
or even
func setPosition(x, y, z float) { /* ... */ }
Default values can be specified for each argument, and if the argument is missing when the function is called, then it will automatically be filled in with the default value.
func sum(a double = 0.0, b double = 0.0, c double = 0.0) double {
return a + b + c
}
Default values can be specified per argument or argument group, depending on syntax.
For example, the following applies to all a
, b
, and c
func sumB(a, b, c double = 0.0) double {
return a + b + c
}
But default values can also be specified per argument when using a shared argument type:
func sumC(a = 1.0, b = 2.0, c double = 3.0) double {
return a + b + c
}
Default argument values are evaluated in the context of the callee, and are unique to each call.
import basics
func getDefaultName() String {
print("Getting default name...")
return "Isaac"
}
func welcome(name String = getDefaultName()){
print("Welcome " + name)
}
func main {
welcome()
welcome()
}
Getting default name...
Welcome Isaac
Getting default name...
Welcome Isaac
Arguments that are marked as POD
(plain-old-data), are immune to __pass__
and __defer__
management procedures.
C-style variadic arguments can be used for a function, using ...
func myCStylePrintf(format *ubyte, ...) int { /* ... */ }
C-style variadic arguments do not support structure types, or other composite types. Unless compatibility with C is desired, it is recommended to use Adept-style variadic arguments instead.
Adept-style variadic arguments can be used for a function, using ...
followed by a variable name to hold the variadic argument array.
import VariadicArray
func myPrintf(format String, ... arguments) int { /* ... */ }
For most cases, the type of the variadic argument list received, will be the standard library type VariadicArray
.
The keywords in
, out
, and inout
can be used to help document the data flow of a function.
func sum(in a, b int, out result *int) {
*result = a + b
}
They only exist to help the programmer, and do NOT actually enforce data flows.
See data flow annotations for more information.
The return type is specified after the parameter list. If there isn't any return type, then it can be omitted, or void
can be used in its place.
Additionally, the return type of a function can be prefixed with the exhaustive
keyword to make the return value no-discard. Return values that are no-discard cannot be ignored when calling the function. Forgetting to use the return value of a no-discard function is a compile-time error.
func getName() exhaustive String = "Not using this return value is a compile-time error"
The stdcall
modifier can be used to tell the compiler that a function should use the stdcall calling convention.
stdcall func test(a int, b float) long {
/* ... */
}
The verbatim
modifier can be used to disable auto-generation of certain management procedures. Generally it's used on __pass__
and __defer__
management definitions.
verbatim func __defer__(this *MyStruct) {
// 'verbatim' tells the compiler to not automatically call '__defer__' on children of 'this'
}
verbatim func __pass__(my_struct_value POD MyStruct) MyStruct {
// 'verbatim' tells the compiler to not automatically call '__pass__' on children of 'my_struct_value'
}
The implicit
modifier can be used to promote the __as__
management procedure so the compiler will preform implicit conversions using it.
implicit func __as__(my_number MyNumber) double {
// 'implicit' makes it so that values of type 'MyNumber' can be converted to 'double' without explicitly using 'cast' or 'as'
}
Some functions and methods have special intrinsic purposes.
See intrinsic procedures for more information
Functions defined in other programming languages can be declared using the foreign
keyword.
foreign printf(*ubyte, ...) int
See foreign functions for more information.
Functions can be made externally visible by using the external
keyword
external func sum(a, b int) int {
return a + b
}
See externally visible functions for more information.
When a function only performs a simple operation to return a value, it can be abbreviated by substituting the code block with an =
sign followed by an expression that represents the return value
For example
func sum(a, b int) int {
return a + b
}
can be abbreviated as
func sum(a, b int) int = a + b
Polymorphic functions are functions that can operate on multiple types.
They are declared like normal functions, expect they use polymorphic types within their arguments and/or return type.
func difference(a, b $T) $T {
return a - b
}
Function aliases can be used to have multiple names refer to the same function definition
func alias println(String) => print
See function aliases for more information.
When multiple possible functions exist with the same name, then the rules for function resolution will be applied to determine which one is used.
See function calls for more information.
Virtual methods are methods which are dispatched at runtime. They can be defined on classes. See classes for more information.
import basics
class Shape () {
constructor {}
virtual func getArea() float = 0.0
}
class Rectangle extends Shape (w, h float) {
constructor(w, h float){
this.w = w
this.h = h
}
override func getArea() float = this.w * this.h
}
func main {
shape *Shape = new Rectangle(5.0f, 10.0f)
defer delete shape
print(shape.getArea())
}
Functions may be marked with = delete
after their return type to disallow using them.
func youCannotCallThisFunction(a, b int) int = delete
func youCannotCallThisFunctionAlso(a, b int) = delete
func youCannotCallThisFunctionEither(a, b int) int = delete { return a + b }
Trying to call a disallowed function is a compile-time error.
Disallowing functions is most useful for disabling assignment of certain types. For example,
func __assign__(this *UnassignableType, _other POD UnassignableType) = delete
Trying to assign a type regularly that has __assign__
disallowed is a compile-time error.
Constructors can be created using the constructor
keyword inside of composite types such as a classes or structs.
struct Rectangle (w, h float) {
constructor(w, h float) {
this.w = w
this.h = h
}
constructor(size float) {
this.__constructor__(size, size)
}
}
func main {
rectangle1 Rectangle(100, 50)
rectangle2 Rectangle = Rectangle(75, 50)
rectangle3 Rectangle
rectangle3.__constructor__(500)
rectangle4 *Rectangle = new Rectangle(640, 480)
defer delete rectangle4
}
Constructors automatically generate a function that has the same name as the target type, and which returns a constructed instance of that type.
Constructors also come with a companion __constructor__
method, which can be used to call a constructor on any value
Constructors can use the immediate construction syntax:
rectangle Rectangle(100, 50)
square *Rectangle = new Rectangle(400, 400)
defer {
square.__defer__()
delete square
}
Zero-initialization is still the default, constructors don't apply to naked definitions, e.g. my_value MyType
is zero-initialized and not constructed.