From 1f52dd3eddfd795ff51a45de1213d9ca27115fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Sun, 22 Mar 2020 14:56:38 +0100 Subject: [PATCH] Created Functions and methods (markdown) --- Functions-and-methods.md | 558 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 558 insertions(+) create mode 100644 Functions-and-methods.md diff --git a/Functions-and-methods.md b/Functions-and-methods.md new file mode 100644 index 0000000..a6a8098 --- /dev/null +++ b/Functions-and-methods.md @@ -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: , 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) { + # '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) { + # '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>>) { +} + +# 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: ) { + # '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) { + # 'x' can be only a map with strings as keys and notes as values +} + +function bar(x: map<>) { + # 'x' can be only a map with strings as keys and arbitrary values +} + +function xyz(x: map) { + # 'x' can be only a map with strings and booleans as keys and ints and notes as values +} + +function abc(x: map<>) { + # '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: > = [[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>) { + # 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> with function d() {} +extend map with function e() {} +extend map<><> with function f() {} +extend map<>>>> with function g() {} + +# Incorrect +extend with function h() {} +extend 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 +extend list 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` and `list` (but not `list`) you have +to do it separately: +``` +# This won't work at all +extend , list> as l with function foo() { + # ... +} + +# Instead of that you can do: +extend list as l with function foo() { + # ... +} + +extend list 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: [, ] + +Stack trace: +[0] ::() +``` \ No newline at end of file