In Hades, function declarations don't contain the return type of the functions. Neither do the datatypes of the input values have to be defined.
Statically typed parameters
A function which is defined with static types, can only be called with those lines. If a function definition for a function with the types specified in the function call does not exist, execution will fail. The return type of a function is declared with ->. If a function doesn't return anything: ->none (although technically a function never doesn't return anything, a fallback 0 is always returned but none warns you if you use the default 0). Only if a function is fully typed, it can be JITed.
Example
with console from std:iofuncadd(a int, b int) //can also be written as `func add(a b int) console.out("Adds two ints") put a + bendfuncadd(a string, b string) -> string console.out("Concatenates two strings") put "{} {}".format(a,b)endadd(2,2)//Output: Adds two intsadd("Hello","world")//Output: Concatenates two stringsadd(1.5,1.5)//Execution fails
Dynamically-typed parameters
This function can be used with every datatype.
Example
with console from std:iofuncadd(a,b)var result = a + b console.out(result) put resultendadd(2,2)//Output: 4add("Hello ","world")//Output: Hello worldadd(1.5,1.5)//Output: 3.0
Varargs
Varargs allow for method invocation with multiple parameters which are treated as a single parameter array by the function. A function can only have one vararg parameter.
Example
with console from std:iofuncsum(...a int)var result =0for(var i in a) result += i end put resultendfuncprint(...a)for(var arg in a) console.out(arg) endendfuncprint(...a, times int)for(var i in range(1,times))for(var arg in a) console.out(arg) end endendconsole.out(sum(1, 2, 3, 4, 5, 6, 7, 8, 9))print("Hello", ",", "world")print("Hello", "," ,"world", 5/*this is the 'times' parameter*/)
Function guards
With function guards, an initial condition has to be fulfilled for the function to be called. If the condition is not fulfilled, another function (ordered by declaration) with the same name and different, or no, function guard is called.
Example
with console from std:iofuncmyFunction(a int) requires a < 10 console.out("a is smaller than 10")endfuncmyFunction(a int) -> none requires a is 11 console.out("a is 11")endfuncmyFunction(a int) requires a > 11 and a < 21 console.out("a is greater than 11 and smaller than 21")endfuncmyFunction(a int)//This default function is called when every condition is false console.out("a is "+ a)endmyFunction(1)//Output: a is smaller than 10myFunction(11)//Output: a is 11myFunction(15)//Output: a is greater than 11 and smaller than 21myFunction(50)//Output: a is 50
Function guards by match
In addition to the normal function guards, Hades also offers function guards by object/list match. To use this feature, one must first define the function.
with console from std:iofuncdoSomething(person);funcdoSomething(person :=Employee{department:"IT"}) console.out("{} works in IT".format(person.firstname))endfuncdoSomething(Employee{department: "Finance", firstname := firstname}) console.out("{} works in Finance".format(firstname))enddoSomething(Employee("John", "Finance"))//Output: John works in FinancedoSomething(Employee("Steve", "IT"))//Output: Steve works in IT
with console from std:iofuncgetStatus(status);funcgetStatus(status :=:ok) console.out("Everything went well")endfuncgetStatus(:error) console.out("An error occured")endfuncgetStatus(status) console.out("Unrecognized status code")endgetStatus(:ok)//Output: Everything went wellgetStatus(:foo)//Output: Unrecognized status code
with console from std:iofunconReceive(e);funconReceive(e := {:ok, message}) console.out("Received {}".format(message))endfunconReceive({:error, _}) console.out("Error while receiving message")end
Custom blocks
Custom blocks are syntactic sugar that behave like lambdas but are written in do syntax.
Example
funcforEach(arr *any[*], executorlambda::(any)->any) put arr |>map(executor)endforEach({1, 2, 3})do|x| console.out(x)endvar strings =forEach({1, 2, 3})do|x| put x.toString()endfuncdoStuff(executorlambda::(none)->none)executor()enddoStuff do console.out("Hello")end
Nested functions
As with normal function declarations, nested functions can either explicitly name a type, or not:
Example
with math as m from std:mathfuncdoMath(a int)funcroot(b int) put m.sqrt(b) endfuncsquare(b) put b * b end put square(a)+root(a)end
Access modifiers
Functions can have access modifiers. If they do, they follow the same rules as variables. See Non-local Variables.
Fixed function
Fixed functions are like static function in Java or C#. One can only declare fixed functions in classes, because in scripts or mixed files, every function which is outside a class is accessible.
Overriding functions
One can override functions (both built-in, as well as inherited) by appending ! to the func statement. Some internal functions are not overridable because of the way they're implemented. These functions are type, nameof, send and self.
Example
with assert from std:testingclassCar ... func! toString() -> string put "My custom string" endendassert.equal(Car().toString(), "My custom string")assert.equal("{}".format(Car()), "My custom string")
Default values
When a function that has default values is being used, the sequence of the parameters which don't have default values stays the same as an invocation without overriding these defaults.
Example
funcfunctionWithDefaultValues(a,b=1,c,d="d",e,f=true)...endfunctionWithDefaultValues(b=2, d="D", "This is 'a'", "This is 'c'", f=false, "This is 'e'")