A statically typed, general-purpose programming language, crafted for simplicity and performance.
"Trust the programmer" - corx
A function declaration involves, defining return type, then name / tag / identifier of the function and it's parameters. If there's no parameters, it can be left empty or pass void to that function to explicitly specify that, this function doesn't accept any parameters.
A function that doesn't return anything and takes to parameter.
void width() {
print("Hello, there");
}
A function that returns int
and takes no parameter.
int width(void) {
return 50;
}
This is identical to above:
int width() {
return 50;
}
In this example, we define a simple function square
that takes an integer num
as a
parameter and returns its square. The function is called with the value 5
, and the result is
printed, showing 25
.
In corx
parameters are pass-by-value by default.
int square(int num) {
return num * num;
}
int num = 5;
int sqr = square(num);
print(sqr); # prints 25
In corx
, you can define default arguments for functions, allowing you to call the function
without specifying all the parameters. If a parameter is not provided, the default value is used.
float add(float a, int b = 5.3) {
return a + b;
}
float res = add(2.3);
print(res); # prints 7.6
In this example, the function add
is called with one argument (2.3)
, and the
default value 5.3
is used for b
.
Overriding default argument:
float add(float a, int b = 5.3) {
return a + b;
}
float res = add(2.3, 1.2);
print(res); # prints 3.4
Here, the default value of b
is overridden by the provided argument 1.2
.
In corx
, parameters are passed by value by default, meaning that a copy of the argument is used
within the function. To pass a parameter by reference, the (@
) symbol is used. This allows the
function to modify the original argument.
Default behavior:
void increment(int num) {
num++; # temporary copy gets incremented
}
int amount = 5;
print(amount); # prints 5
increment(amount);
print(amount); # prints 5
In this example, the value of amount
remains unchanged because the increment
function works on a copy of the argument.
When passed by reference:
void increment(int @num) {
num++; # original value gets incremented
}
int amount = 5;
print(amount); # prints 5
increment(amount);
print(amount); # prints 6
By using the (@
) symbol, the increment
function modifies the original
amount
variable, and the value is updated accordingly.
int @increment(int @num) {
num++; # original value gets incremented
return num; # return reference to 'num'
}
int amount = 5;
print(amount); # prints 5
int @res = increment(amount); # 'res' gets the modified value of 'amount' (6)
print(res); # prints 6
res++; # increment the value of 'res'
print(amount); # prints 7, as 'res' is a reference to 'amount'
The following examples demonstrate a mechanism to replace function pointers using named types and type-safe
function selection. This approach emphasizes clarity, simplicity, and alignment with corx
's philosophy.
Basic use of function types to swap functions based on context.
# define operation type for functions that take two ints and return an int
type int operation(int, int);
# addition function
int addition(int a, int b) {
return a + b;
}
# multiplication function
int multiply(int a, int b) {
return a * b;
}
# calculate applies the given operation to two numbers
int calculate(int a, int b, operation op) {
return op(a, b);
}
# example usage
int add = calculate(5, 6, addition); # 11
int mul = calculate(5, 6, multiply); # 30
Example of swapping functions using a selector with callback functionality.
# selector for choosing operation based on opcode
type int operation(int, int);
int addition(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
# select operation based on opcode
operation select(int opcode) {
switch(opcode) {
case 1:
return addition; # add
case 2:
return multiply; # multiply
default:
return null; # invalid
}
}
# usage of select function
operation op = select(1); # select addition
int res = op(2, 3);
print(res); # 5
Demonstrates context-driven function swapping using a calculation function.
# define operation and calculate types
type int operation(int, int);
type int calculate(int, int, operation);
int addition(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
# calculate applies operation to numbers
int calculate(int a, int b, operation op) {
return op(a, b);
}
# render calls calculate with different operations
void render(int a, int b, calculate calc) {
int add = calc(a, b, addition); # add
print("addition: ", add);
int mul = calc(a, b, multiply); # multiply
print("multiplication: ", mul);
}
render(3, 4, calculate); # addition: 7 multiplication: 12
A forward declaration allows you to declare a function prototype before its usage. It specifies the function's return type, name, and parameter list without providing the implementation. This is useful when a function is used before being defined.
int add(int a, int b); # with parameter names
# or
int add(int, int); # without parameter names
Forward declarations ensure the compiler knows about the function's signature ahead of time, enabling better modular code organization.
An inline function is a hint to the compiler to replace the function call with the actual code of the function to reduce overhead. This can improve performance for small, frequently called functions.
type int fnt(int); # function type
# inlined square function
inline int square(int a) {
return a * a;
}
# function to use the callback
int usecb(fnt fn, int x) {
return fn(x);
}
# using callback with the square function
int cb = usecb(square, 3); # 9
print(cb); # prints 9
Here, the square
function is marked as inline
. While the behavior of the program
remains the same, the compiler might optimize it by replacing the function call with its implementation,
potentially reducing execution time.
Function overloading allows multiple functions to have the same name but differ in the number or type of their parameters. The correct function is selected based on the argument types during the function call.
int add(int a, int b) {
return a + b;
}
float add(float a, float b) {
return a + b;
}
int int_result = add(2, 3); # calls int version, result is 5
float float_result = add(2.5, 3.5); # calls float version, result is 6.0
A variadic function is a function that accepts a variable number of arguments. It allows you to pass any number of arguments of different types.
With single type:
int sum(int args...) { # variable name, only int type is allowed
vargs data = vainit(args);
int res = 0;
foreach (arg in data) {
print(arg);
}
# automatic cleanup any data existed
return res;
}
Mixed numeric types:
float sum(void args...) { # variable name, any type is allowed
vargs data = vainit(args);
float res = 0;
foreach (arg in data) {
res += arg; # other type has undefined behavior
}
# automatic cleanup any data existed
return res;
}
Mixed types:
void print(void args...) { # variable name, any type is allowed
# process with extra data using same type 'vargs'
# for both 'vainit' and 'vaxinit' is possible due to polymorphism
vargs data = vaxinit(args);
foreach (arg in data) {
if (typeof arg = 5) { # typeof returns type id
print("string");
}
}
# automatic cleanup any data existed
}
const
KeywordThe const
keyword can be used with function parameters to ensure that they cannot be modified
within the function. This is typically used for parameters passed by reference.
void render(const int @num) {
print(num); # num cannot be modified
}
int value = 10;
render(value); # prints 10