Skip to content

Functions

Isaac Shelton edited this page Sep 8, 2023 · 8 revisions

Functions

Functions are blocks of code that take zero or more parameters and optionally return a value.

Declaration

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.

Standard Syntax

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

Alternative Syntax 1

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

Alternative Syntax 2

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

Parameters

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.

Examples (Standard Syntax)
(name String)
(name String, age int)
(firstname, lastname String)
Examples (Alternative Syntax)
(name: String)
(name: String, age: int)
(firstname, lastname: String)

Compound Parameters

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

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

POD Parameters

Arguments that are marked as POD (plain-old-data), are immune to __pass__ and __defer__ management procedures.

C-style Variadic Arguments

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

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.

Parameter Data-Flow Annotations

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.

Return Type

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"

Modifiers

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'
}

Intrinsic Procedures

Some functions and methods have special intrinsic purposes.

See intrinsic procedures for more information

Foreign Functions

Functions defined in other programming languages can be declared using the foreign keyword.

foreign printf(*ubyte, ...) int

See foreign functions for more information.

Externally Visible Functions

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.

Single Line Functions

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

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

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.

Multiple Possibilities

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

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())
}

Disallowed functions

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

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.

Clone this wiki locally