Created Functions and methods (markdown)
558
Functions-and-methods.md
Normal file
558
Functions-and-methods.md
Normal file
@@ -0,0 +1,558 @@
|
||||
Function is a code snippet that takes some arguments and produces result.
|
||||
Both arguments and result can be optional.
|
||||
Method in turn is a special function that is called in behalf of some
|
||||
object.
|
||||
Function (and method of course) can be invoked using its name and optional arguments
|
||||
separated with commas (`,`) bounded on both sides with parentheses (`(` and `)`).
|
||||
Even if no arguments are passed you have to put empty arguments list: `()`.
|
||||
|
||||
Some functions are available out-of-the-box as they are implemented
|
||||
to SMNP tool. Set of such functions is called *standard library* and is provided in the form of so-called _modules_ (you can read more about standard library and modules in later section).
|
||||
You are able also to create custom functions and methods (it'll be covered in next section).
|
||||
|
||||
As you can see on below examples implemented functions and methods mechanism
|
||||
is really similar to other popular languages, like Java or Python.
|
||||
|
||||
Examples:
|
||||
```
|
||||
# Invoking function 'println' without any arguments
|
||||
println();
|
||||
|
||||
# Invoking function 'println' with 1 argument
|
||||
println("Hello, world!"); # Hello, world
|
||||
|
||||
# Invoking function 'println' with 2 argument
|
||||
println(1, 2); # 12
|
||||
|
||||
# Invoking function 'println' with 2 argument
|
||||
# Invoked function produces also result which
|
||||
# is being assigned to variable 'newNote1'
|
||||
newNote1 = transpose(2, @c);
|
||||
|
||||
# Method equivalent of function above
|
||||
newNote2 = @c.transpose(2);
|
||||
|
||||
# Another example of method
|
||||
firstElementOfList = [14, 3, 20, -4].get(0);
|
||||
|
||||
|
||||
println(newNote1 == newNote2); # true
|
||||
```
|
||||
|
||||
Note, that some functions or methods don't return anything.
|
||||
In this case expecting any returned value can throw an exception.
|
||||
```
|
||||
# 'println' is example function, that doesn't return anything
|
||||
# in this case following instruction will raise an error
|
||||
println(println()); # error
|
||||
|
||||
# other examples
|
||||
x = println(); # error
|
||||
|
||||
println([1, 2, 3, println(), 5, 6]); # error
|
||||
```
|
||||
|
||||
# Function/method signature
|
||||
Signature is feature of both functions and methods that makes them unique.
|
||||
Signature consists of function/method name, function/method arguments
|
||||
and applicable type in case of method (however, technically the _signature_ in the SMNP source code
|
||||
is referred only to arguments list).
|
||||
|
||||
# Custom methods and functions
|
||||
SMNP language introduces possibility to define custom functions and methods.
|
||||
They can be invoked later in the same way as regular functions/methods
|
||||
provided by SMNP standard library and other modules.
|
||||
|
||||
Functions and methods can be defined only at top-level of the code, which means
|
||||
they cannot be nested in any blocks.
|
||||
|
||||
### Function definition
|
||||
Functions can be defined with `function` keyword, like it is shown on following example:
|
||||
```
|
||||
function multipleBy2(number) {
|
||||
return 2*number;
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
x = multipleBy2(2); # x = 4
|
||||
x = multipleBy2(14); # x = 28
|
||||
x = multipleBy2("hey"); # in spite of correctness it will cause an error
|
||||
# because string argument doesn't support '*' operator
|
||||
|
||||
# Incorrect invocations
|
||||
x = multipleBy2(1, 2); # it'll cause Invocation Error because of signatures mismatch
|
||||
x = multipleBy2(); # as above
|
||||
```
|
||||
Thanks to `return` keyword you can produce an output value basing on e.g. passed arguments.
|
||||
Example above takes one arbitrary argument, multiplies it by 2 and then returns a result.
|
||||
|
||||
#### Explicit argument types
|
||||
Because arbitrary argument can be passed, the `multipleBy2` function can throw an error
|
||||
in case of trying to multiple e.g. note or string, that don't support `*` operator.
|
||||
In this case you can put a constraint to the argument which accepts only chosen types (using Pascal/Kotlin-like syntax):
|
||||
```
|
||||
function multipleBy2(number: int) {
|
||||
return 2*number;
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
x = multipleBy2(2); # x = 4
|
||||
x = multipleBy2(14); # x = 28
|
||||
|
||||
# Incorrect invocations
|
||||
x = multipleBy2(1, 2); # it'll cause Invocation Error because of signatures mismatch
|
||||
x = multipleBy2(); # as above
|
||||
x = multipleBy2("hey"); # as above
|
||||
```
|
||||
Function `multipleBy2` will work only with int argument now. Any attempt to invoke it
|
||||
with no-int values will cause Invocation Error related to mismatched signatures.
|
||||
|
||||
Of course, you are still able to create functions without any arguments or mix their types:
|
||||
```
|
||||
function noArgs() {
|
||||
println("Hello, I don't accept any arguments.");
|
||||
println("Also see that I don't return anything!");
|
||||
}
|
||||
|
||||
function mixedArguments(a1: int, a2: note, a3) {
|
||||
println("See, " + a1 + " is an int!");
|
||||
println("And " + a2 + " is a note.");
|
||||
println("Type of third argument is " + typeOf(a3));
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
mixedArgument(1, @c, "hey");
|
||||
mixedArguments(14, @Gb:4d, true);
|
||||
|
||||
# Incorrect invocations
|
||||
mixedArgument(@c, @c, @c);
|
||||
mixedArgument(1, 1, 1);
|
||||
|
||||
```
|
||||
#### Functions returning nothing
|
||||
Notice also that functions presented in previous example don't return anything.
|
||||
Technically they **do** actually return a `void` type and it is transparent to SMNP user. You should remember, that
|
||||
you can expect value produced by function only and only if `return` statement
|
||||
is declared in invoked function and the statement **has been called**. Otherwise
|
||||
function being invoked will return `void` type which cannot act as expression.
|
||||
|
||||
#### Ambiguous arguments
|
||||
SMNP language allows you also to declare more than one argument that functions can accept.
|
||||
Accepted types can be declared using _union construction_ which consists of types separated with commas (`,`) and bounded on both sides with `<` and `>` characters.
|
||||
|
||||
Example:
|
||||
```
|
||||
function foo(x: <string, bool, int, float>, y: note, z) {
|
||||
# 'x' can be either string, bool, int or float
|
||||
# whereas 'y' can be only a note
|
||||
# and 'z' can be of any available type
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo("hey", @c, [1, 2, true, [@c], { a -> 1, b -> 2 }]);
|
||||
foo(true, @f#:4d, "hello");
|
||||
foo(14, @B3:2d, @c#:16d);
|
||||
foo(1.4, @h5, 3.14);
|
||||
|
||||
# Incorrect invocations
|
||||
foo("abc", @cb, 10);
|
||||
foo("hey", 20, 10);
|
||||
foo([1, 2, 3], @f, 10);
|
||||
```
|
||||
|
||||
#### Specified lists
|
||||
SMNP language allows you to specify what objects can be contained in lists.
|
||||
It can be done with construction similar to _union construction_.
|
||||
Accepted types are separated with commas (`,`), bounded on both sides with `<` and `>` characters and placed
|
||||
next to `list` keyword:
|
||||
```
|
||||
function foo(x: list<int>) {
|
||||
# 'x' can be only list of ints
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo([1, -2, 3]);
|
||||
foo([0]);
|
||||
foo([]);
|
||||
|
||||
# Incorrect invocations
|
||||
foo(1);
|
||||
foo(2, 3);
|
||||
foo([ 1, 2, @c ]);
|
||||
```
|
||||
|
||||
You are still able to use multiple type as constraints:
|
||||
```
|
||||
function foo(x: list<int, note>) {
|
||||
# 'x' can be only list of ints and notes, nothing else
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo([1, @c, 4]);
|
||||
foo([]);
|
||||
foo([0]);
|
||||
foo([@d#]);
|
||||
|
||||
# Incorrect invocations
|
||||
foo([1, @c, true]);
|
||||
foo(@c, 1);
|
||||
foo({ @c -> 1 });
|
||||
```
|
||||
|
||||
List specifier can be empty or even be absent. In this case list can contain
|
||||
any available types:
|
||||
```
|
||||
function foo(x: list<>) {
|
||||
# 'x' can be only a list containing arbitrary objects
|
||||
}
|
||||
|
||||
# the same function can be implemented as
|
||||
|
||||
function foo(x: list) {
|
||||
# ...
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo([1, @c, true]);
|
||||
foo([]);
|
||||
foo(["abc", 38, @g#:32d, false, [[[]]], { a -> 1, @d -> 2, true -> {} }]);
|
||||
|
||||
# Incorrect invocations
|
||||
foo({});
|
||||
foo([1, @c, true], 1);
|
||||
foo(true);
|
||||
```
|
||||
|
||||
Specifiers can be nested - you are able to create e.g. list of lists of lists of ints etc.
|
||||
```
|
||||
function foo(x: list<list<list<int>>>) {
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]);
|
||||
|
||||
# Incorrect invocations
|
||||
foo([[[1, 2], [3, 4]], [[5, 6], [7, 8]], 9]);
|
||||
```
|
||||
|
||||
Of course ambiguous arguments can include specified lists:
|
||||
```
|
||||
function foo(x: <int, note, list<int, note>) {
|
||||
# 'x' can be int, note or list containing only ints and notes
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo(@c);
|
||||
foo(1);
|
||||
foo([]);
|
||||
foo([2, @G]);
|
||||
foo([@h]);
|
||||
|
||||
# Incorrect invocations
|
||||
foo(1.0);
|
||||
foo([true]);
|
||||
```
|
||||
|
||||
#### Specified maps
|
||||
Maps can be also specified. All rules related to specified lists are also applicable for maps.
|
||||
The only difference is map supports **2** specifiers: first one for keys and second one for values.
|
||||
|
||||
Example:
|
||||
```
|
||||
function foo(x: map<string><note>) {
|
||||
# 'x' can be only a map with strings as keys and notes as values
|
||||
}
|
||||
|
||||
function bar(x: map<string><>) {
|
||||
# 'x' can be only a map with strings as keys and arbitrary values
|
||||
}
|
||||
|
||||
function xyz(x: map<string, bool><int, note>) {
|
||||
# 'x' can be only a map with strings and booleans as keys and ints and notes as values
|
||||
}
|
||||
|
||||
function abc(x: map<><int, bool>) {
|
||||
# 'x' can be only a map with arbitrary keys and ints with booleans as values
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo({ c -> @c, d -> @d });
|
||||
bar({ a -> @c, b -> 1, c -> true, d -> map, e -> { x -> [] });
|
||||
xyz({ a -> 1, true -> @c, false -> 2, b -> @d });
|
||||
abc({ a -> true, 1 -> false, @c -> 10, true -> 14 });
|
||||
|
||||
# Incorrect invocations
|
||||
foo({ c -> @c, @d -> @d, @c -> "hey" });
|
||||
bar({ a -> @c, @d -> @c });
|
||||
xyz({ 1 -> @c, 2 -> 3, 3 -> bool });
|
||||
abc({ a -> true, false -> @c });
|
||||
```
|
||||
|
||||
#### Optional arguments
|
||||
SMNP language allows you also to use default arguments.
|
||||
Optional arguments are special kind of arguments that can have default value and are
|
||||
totally optional to pass during invocation. Syntax of optional arguments is similar
|
||||
to variable assignment syntax.
|
||||
|
||||
Example:
|
||||
```
|
||||
function foo(x = 10) {
|
||||
# 'x' can be of any available type.
|
||||
# You can override default value but you don't have to.
|
||||
# In this case default value will be used.
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
y = foo(); # y = 10
|
||||
y = foo(10); # y = 10
|
||||
y = foo(true); # y = true
|
||||
```
|
||||
|
||||
All kinds of arguments mentioned so far can be optional:
|
||||
```
|
||||
function foo(x = 1, y: int = 14, z: <note, list<list<int, note>> = [[1, @c], [@d]]) {
|
||||
}
|
||||
|
||||
# Correct invocations
|
||||
foo();
|
||||
foo(2);
|
||||
foo(10, 11);
|
||||
foo(-2, 33, @c);
|
||||
foo(-2, 33, [[1, @d, @f#], [4, 3], [@c, @d, @e]);
|
||||
foo(0, 0, []);
|
||||
foo(0, 0, [[]]);
|
||||
|
||||
# Incorrect invocations
|
||||
foo(true);
|
||||
foo(1, 0.5);
|
||||
foo(10, 2, 13);
|
||||
foo(0, 0, [], 3);
|
||||
```
|
||||
|
||||
As you can see, you can have as many optional arguments as you want.
|
||||
Just remember, that after first declared optional argument you can't use regular
|
||||
arguments in the same signature anymore.
|
||||
```
|
||||
# Correct signatures
|
||||
function foo(a, b, c = 0) {}
|
||||
function bar(a, b = "hello", c = true) {}
|
||||
function xyz(a = 14, b = [12.5], c = @c) {}
|
||||
|
||||
# Incorrect signatures (will throw an error)
|
||||
function abc(a = 0, b) {}
|
||||
function def(a, b, c = 0, d = true, e, f = @G#) {}
|
||||
```
|
||||
In other words, optional arguments should be declared at the tail of function signature.
|
||||
|
||||
#### Varargs
|
||||
SMNP language also supports functions' signatures with arbitrary length.
|
||||
This is exactly how `println` or `print` functions work.
|
||||
You can pass as many arguments as you want and functions are still able to handle it.
|
||||
SMNP language provides a special operator `...` that enables you to have function
|
||||
with variable number of arguments.
|
||||
|
||||
Remember that:
|
||||
* you can have only one vararg at the signature
|
||||
* vararg should be placed at the very end of signature
|
||||
* you can't mix varargs with optional arguments in one signature.
|
||||
|
||||
After invocation, all matched arguments are collected into one list which is passed
|
||||
as vararg.
|
||||
|
||||
Example:
|
||||
```
|
||||
function foo(a, b, ...c) {
|
||||
# 'c' is a list of arguments passed after 'a' and 'b'
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
# Correct invocations:
|
||||
x = foo(0, 1); # x = []
|
||||
x = foo(1, 2, 3, 4); # x = [3, 4]
|
||||
x = foo(true, false, @c, [3.14, 5, "abc"], 2); # x = [@c, [3.14, 5, "abc"], 2]
|
||||
|
||||
# Incorrect invocations:
|
||||
foo();
|
||||
foo(true);
|
||||
```
|
||||
|
||||
Same as optional arguments, vararg can work with any previously mentioned kinds
|
||||
of arguments (except optional arguments of course).
|
||||
```
|
||||
function foo(a, b: note, ...c: map<string><list<int, note>>) {
|
||||
# after two arguments you can pass any number of
|
||||
# maps with strings as keys and list of ints and notes as values
|
||||
}
|
||||
|
||||
# Correct invocations:
|
||||
foo(1, @c, { a -> [@d], b -> [@c, @g] }, { a -> [], b -> [@e] });
|
||||
foo(1, @c, {}, {}, {}, {}, {}, {}, {}, {});
|
||||
|
||||
# Incorrect invocations:
|
||||
foo();
|
||||
foo(1, @c, { a -> [@d], b -> [@c, @g] }, { a -> [], b -> @e });
|
||||
```
|
||||
|
||||
### Method definition
|
||||
All rules related to defining custom functions are applicable to methods either.
|
||||
The difference is method can be invoked in a context of some object.
|
||||
That's why methods can be defined only as a part of some type.
|
||||
|
||||
#### `extend` statement
|
||||
SMNP language introduces `extend` statement which allows you to add custom
|
||||
methods to existing types. All you need is to precise desired type and define function
|
||||
which will be working as new method. In the body of declared method, the extending object is available through `this` special identifier.
|
||||
Following listing presents a syntax of `extend` statement:
|
||||
```
|
||||
extend int with function multipleBy(x: int) {
|
||||
return this*x;
|
||||
}
|
||||
|
||||
# From now on you can use new method as follows:
|
||||
x = 10.multipleBy(2); # x = 20
|
||||
y = x.multipleBy(4); # y = 80
|
||||
```
|
||||
|
||||
As mentioned before, all rules related to functions are applicable here, so there
|
||||
is no need to cover `function multipleBy(...) {...}` statement again.
|
||||
|
||||
Note, that type being extended must be declared only as a simple data type or map/list with optional
|
||||
|
||||
Examples:
|
||||
```
|
||||
# Correct
|
||||
extend int with function a() {}
|
||||
extend list with function b() {}
|
||||
extend list<> with function c() {}
|
||||
extend list<list<note, int>> with function d() {}
|
||||
extend map<string><note> with function e() {}
|
||||
extend map<><> with function f() {}
|
||||
extend map<><list<map<><note, string, list<int>>>> with function g() {}
|
||||
|
||||
# Incorrect
|
||||
extend <string, int> with function h() {}
|
||||
extend <int> with function i() {}
|
||||
extend <> with function j() {}
|
||||
extend int with function k() {}
|
||||
```
|
||||
|
||||
Because extending statement supports list/map specifiers, you can define
|
||||
methods that are applicable only for list/map with specified content:
|
||||
```
|
||||
# Extends all lists, no matter of content
|
||||
extend list with function foo() {
|
||||
println("foo()");
|
||||
}
|
||||
|
||||
# Extends only list<int>
|
||||
extend list<int> with function bar() {
|
||||
println("bar()");
|
||||
}
|
||||
|
||||
l1 = [1, 2, 3, 4];
|
||||
l2 = [1, 2, @c, 4];
|
||||
l3 = ["a", "b", "c", "d"];
|
||||
|
||||
l1.foo(); # foo()
|
||||
l2.foo(); # foo()
|
||||
l3.foo(); # foo()
|
||||
|
||||
l1.bar(); # bar()
|
||||
l2.bar(); # error!
|
||||
l3.bar(); # error!
|
||||
```
|
||||
|
||||
Because SMNP doesn't allow you to extend multiple types in one instruction, if you want
|
||||
extend for example `list<int>` and `list<note>` (but not `list<int, note>`) you have
|
||||
to do it separately:
|
||||
```
|
||||
# This won't work at all
|
||||
extend <list<int>, list<note>> as l with function foo() {
|
||||
# ...
|
||||
}
|
||||
|
||||
# Instead of that you can do:
|
||||
extend list<int> as l with function foo() {
|
||||
# ...
|
||||
}
|
||||
|
||||
extend list<note> as l with function foo() {
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
#### `extend` block
|
||||
In case of extending type with more than one function you can use block of methods.
|
||||
|
||||
Example:
|
||||
```
|
||||
extend int {
|
||||
function multipleBy2() {
|
||||
return 2*this;
|
||||
}
|
||||
|
||||
function multipleBy4() {
|
||||
return 4*this;
|
||||
}
|
||||
}
|
||||
|
||||
# Construction above is equivalent of following code:
|
||||
|
||||
extend int with function multipleBy2() {
|
||||
return 2*this;
|
||||
}
|
||||
|
||||
extend int with function multipleBy4() {
|
||||
return 4*this;
|
||||
}
|
||||
```
|
||||
|
||||
# Overloading function/methods
|
||||
~Unfortunately functions/methods overloading
|
||||
is not supported in custom functions/methods so far.~
|
||||
Both functions and methods can be overloaded. It means, you are able
|
||||
to declare multiple functions and methods applicable to the same objects as long as you enforce them to have different signatures.
|
||||
|
||||
Note, that potential signatures' collision is evaluated only at runtime, when method is being called.
|
||||
|
||||
Example:
|
||||
```
|
||||
function display(x: int) {
|
||||
println("int: ", x);
|
||||
}
|
||||
|
||||
function display(x: float) {
|
||||
println("float: ", x);
|
||||
}
|
||||
|
||||
display(14); # int: 14
|
||||
display(14.0); # float: 14.0
|
||||
```
|
||||
|
||||
However:
|
||||
```
|
||||
function display(x) {
|
||||
println("any: ", x);
|
||||
}
|
||||
|
||||
function display(x: float) {
|
||||
println("float: ", x);
|
||||
}
|
||||
|
||||
display(14); # any: 14
|
||||
display(14.0); # error!
|
||||
```
|
||||
The code will end with following error:
|
||||
```
|
||||
Function invocation error
|
||||
Source: /.../.../scratchpad.mus
|
||||
Position: line 16, column 1
|
||||
|
||||
Found 2 functions with name of display, that matched provided arguments: [<root>, <root>]
|
||||
|
||||
Stack trace:
|
||||
[0] <root>::<entrypoint>()
|
||||
```
|
||||
Reference in New Issue
Block a user