# Polymorph Functions

Compile using `blc my-file-name.bl` and run `./out`.

``````// =================================================================================================
// Polymorphic (Generic/Templated) Functions
// =================================================================================================

/*
Use 'blc --no-warning -run polymorph.bl' to execute this file.
*/

#import "std/array"

intro :: fn () {
// Consider the following example; we need simple 'compare' function to perform comparison of two
// values.
compare :: fn (a: s32, b: s32) bool {
return a == b;
};

// Now we can call this function with any numbers.
a1: s32 = 10;
b1: s32 = 20;
result1 := compare(a1, b1);
print("compare(%, %) = %\n", a1, b1, result1);

// This is great, but what if we want to do the same with floating point values?
a2: f32 = 10.f;
b2: f32 = 20.f;
// result2 := compare(a, b); // This wont compile, the function expects 's32' not 'f32' numbers.

// In this case we can write another 'compare' function taking 'f32' numbers instead of 's32'
// or we can use polymorphic function in this case and let the compiler do all work.
compare_poly :: fn (a: ?T, b: T) bool {
return a == b;
};

// This function does not exist until it's used somewhere, we can think about it as if it was
// a recipe for 'compare' function. Type '?T' (it can be any other name '?TValue', '?TKey' etc) is
// replaced with the actual type used for the argument 'a' when the function is called. The
// question mark before the type name 'T' here is important, it means basically that 'T' can
// be replaced. However, all other 'T' in the argument list are just references to the actual
// replaced type used for 'a' argument.
// So if type of 'a' argument is 's32', the type of 'b' is 's32' also.

// Use of the function.
a3: s32 = 10;
b3: s32 = 20;
result3 := compare_poly(a3, b3);
print("compare_poly(%, %) = %\n", a3, b3, result3);

// Since the type of 'b' is dependent on the type of 'a' argument, the following code would
// not compile.
a4: s32 = 10;
b4: f32 = 20.f; // using floating point number
// result4 := compare_poly(a4, b4); // Compiler error

/*

polymorph.bl:47:29: error(0035): No implicit cast for type 'f32' and 's32'.
46 |     b4: f32 = 20.f; // using floating point number
>  47 |     result4 := compare_poly(a4, b4); // Compiler error
|                                 ^^
48 | }

*/

// But we call call the same function with different value types now.
a5: f32 = 10.f;
b5: f32 = 20.f;
result5 := compare_poly(a5, b5);
print("compare_poly(%, %) = %\n", a5, b5, result5);

// One important thing to know is that we have internally two 'compare_poly' functions generated
// by the compiler. One for 's32' and one for 'f32' types. When we call 'compare_poly' again with
// 'f32' values, compiler will try to reuse already existing function if possible (same for
// 's32').

// What if we call the 'compare_poly' function with arguments which cannot be compared by '=='
// operator?

a6: string_view = "hello";
b6: string_view = "world";
// result6 := compare_poly(a6, b6); // Compiler error

/*

polymorph.bl:30:18: error(0035): Invalid operation for string_view type.
29 |     compare_poly :: fn (a: ?T, b: T) bool {
>  30 |         return a == b;
|                  ^^
31 |     };

polymorph.bl:29:5: In polymorph of function with substitution: T = string_view;
28 |     // or we can use polymorphic function in this case and let the compiler do all work.
>  29 |     compare_poly :: fn (a: ?T, b: T) bool {
|     ^^^^^^^^^^^^
30 |         return a == b;

polymorph.bl:79:16: First called here:
78 |     b6: string_view = "world";
>  79 |     result6 := compare_poly(a6, b6);
|                ^^^^^^^^^^^^
80 | }

*/

// We can handle such situations quite easily, see the next section.
}

specialization :: fn () {
// Remember that we have separate implementation of our function for every type internally.
// Keeping this in mind, we can create specialization for some types if needed. So how to make
// out 'compare' function to work even with strings?

compare_poly :: fn (a: ?T, b: T) bool {
are_the_same := false;
// #if is evaluated in compile-time and only one of the branches is actually "inserted" into
// the code based on the condition.
// One important think here to know is that the condition value must be known in compile-time,
// in this case the compiler knows that 'T' has been already replaced and does not change in
// the function-type specialization.
#if T == string_view {
// Do this when 'T' is 'string_view'.
are_the_same = std.str_match(a, b);
} else {
// Do this for all other types.
are_the_same = a == b;
}
return are_the_same;
};

a1: s32 = 10;
b1: s32 = 10;

// The function is called with integers so the second branch is used when the function
// implementation is generated.
result1 := compare_poly(a1, b1);
print("compare_poly(%, %) = %\n", a1, b1, result1);

// The same but now with strings.
a2: string_view = "hello";
b2: string_view = "hello";

// The function is called with strings so the first branch is used when the function
// implementation is generated.
result2 := compare_poly(a2, b2);
print("compare_poly(%, %) = %\n", a2, b2, result2);

// So now we can call the "same" function with numbers and integers, but what if we call it with
// structure type?

Data :: struct {
a: s32;
b: bool;
};

a3: Data;
b3: Data;
// result3 := compare_poly(a3, b3); // Compiler error

/*

polymorph.bl:123:30: error(0035): Invalid operation for Data type.
122 |             // Do this for all other types.
>  123 |             are_the_same = a == b;
|                              ^^
124 |         }

polymorph.bl:111:5: In polymorph of function with substitution: T = Data;
110 |
>  111 |     compare_poly :: fn (a: ?T, b: T) bool {
|     ^^^^^^^^^^^^
112 |         are_the_same := false;

polymorph.bl:158:16: First called here:
157 |     b3: Data;
>  158 |     result3 := compare_poly(a3, b3);
|                ^^^^^^^^^^^^
159 | }

*/

// Yes, again the '==' operator is not valid for our 'Data' type. We can consider this as
// mistake in our program and introduce some meaningful error report.

compare_poly_safe :: fn (a: ?T, b: T) bool {
are_the_same := false;
#if T == string_view {
are_the_same = std.str_match(a, b);
} else {
is_number :: T == s32 || T == f32;
#if is_number {
are_the_same = a == b;
} else {
compiler_error("The compare_poly_safe function works only with numbers and strings!");
}
}
return are_the_same;
};

// Let's try it again:

a4: Data;
b4: Data;
// result4 := compare_poly_safe(a4, b4); // Compiler error

/*

polymorph.bl:194:18: error(0084): The compare_poly_safe function works only with numbers and strings!
193 |             } else {
>  194 |                 compiler_error("The compare_poly_safe function works only with numbers and strings!");
|                  ^^^^^
195 |             }

polymorph.bl:185:5: In polymorph of function with substitution: T = Data;
184 |
>  185 |     compare_poly_safe :: fn (a: ?T, b: T) bool {
|     ^^^^^^^^^^^^^^^^^
186 |         are_the_same := false;

polymorph.bl:204:16: First called here:
203 |     b4: Data;
>  204 |     result4 := compare_poly_safe(a4, b4); // Compiler error
|                ^^^^^^^^^^^^^^^^^
205 | }

*/
}

function_interface_matching :: fn () {
// The polymorph type matching can follow type patterns in function declaration. Consider the
// following example:

push_anything :: fn (array: *[..]?T, value: T) {
array_push(array, value);
};

// Such a function accept only pointers to dynamic arrays '*[..]' containing any type '?T'.

numbers: [..]s32;
strings: [..]string_view;

// Cleanup at the end of the scope.
defer array_terminate(&numbers);
defer array_terminate(&strings);

push_anything(&numbers, 10);
push_anything(&strings, "hello");
print("%\n", numbers);
print("%\n", strings);

// Now the 'array' argument must be pointer to any dynamic array every time and value type must
// be the same as the array element type.

// push_anything(numbers, 10); // Compiler error.

/*

polymorph.bl:235:33: error(0081): Cannot deduce polymorph function argument type 'T'. Expected is '*[..]T' but call-side argument type is '[..]s32'.
234 |
>  235 |     push_anything :: fn (array: *[..]?T, value: T) {
|                                 ^
236 |         array_push(array, value);

polymorph.bl:256:19: Called from here.
255 |
>  256 |     push_anything(numbers, 10); // Compiler error.
|                   ^^^^^^^^
257 | }

*/

// push_anything(&numbers, "hello"); // Compiler error.

/*

polymorph.bl:274:31: error(0035): No implicit cast for type 'string_view' and 's32'.
273 |
>  274 |     push_anything(&numbers, "hello"); // Compiler error.
|                              ^^^^^
275 | }

*/
}

function_interface_matching_with_sub_types :: fn () {
// One really cool feature of BL is possibility to access "sub-types"; take a look at following
// example showing naive implementation of hash table:

Table :: struct {
keys: [..]s32;
values: [..]string_view;
};

// In general we can do following to get type of any structure member:

TypeOfKeys :: Table.keys;
TypeOfValues :: Table.values;
print("TypeOfKeys = %\n", TypeOfKeys);
print("TypeOfValues = %\n", TypeOfValues);

// Or go even more deeper.
TypeOfKey :: @Table.keys.ptr; // use @ to convert *T to T
print("TypeOfKey = %\n", TypeOfKey);

// There is probably no need to use this feature like this, but it can be really useful in
// polymorph functions.

// The following function can insert values into our table:
insert :: fn (table: *?T, key: @T.keys.ptr, value: @T.values.ptr) {
array_push(&table.keys, key);
array_push(&table.values, value);
};

// And we also want to lookup our values in the table...
get :: fn (table: *?T, key: @T.keys.ptr) @T.values.ptr {
default_value: @T.values.ptr; // zero initialized by default
loop i := 0; i < table.keys.len; i += 1 {
if table.keys[i] == key {
return table.values[i];
}
}
return default_value;
};

table: Table;
insert(&table, 10, "hello");
insert(&table, 20, "world");

v1 := get(&table, 10);
v2 := get(&table, 20);
print("v1 = %\n", v1);
print("v2 = %\n", v2);

// But wait! Why we should use polymorphic functions like this if type of table is always
// 'Table'?

// Because we can do this:

AnotherTable :: struct {
keys: [..]u64;
values: [..]bool;
};

another_table: AnotherTable;
insert(&another_table, 10, true);
insert(&another_table, 20, false);

v3 := get(&another_table, 10);
v4 := get(&another_table, 20);
print("v3 = %\n", v3);
print("v4 = %\n", v4);

// This is just one step from 'generic' hash table, because we can generate the type of the
// table in compile-time based on some inputs. This is not really part of this demo, but type of
// the table can be generated by compile-time function:

GenericTable :: fn (TKey: type, TValue: type) type #comptime {
// Return new type.
return struct {
keys: [..]TKey;
values: [..]TValue;
};
};

generic_table: GenericTable(s32, string_view);
// The function 'GenericTable' is called during compilation and it's result is used as constant; in
// this case it's type of out table.

// And we can still use the same functions to modify the table.
insert(&generic_table, 10, "hello");
insert(&generic_table, 20, "world");

v5 := get(&generic_table, 10);
v6 := get(&generic_table, 20);
print("v5 = %\n", v5);
print("v6 = %\n", v6);
}

main :: fn () s32 {
intro();
specialization();
function_interface_matching();
function_interface_matching_with_sub_types();
return 0;
}

``````