more docs on functions

This commit is contained in:
cozis
2022-08-18 16:26:37 +02:00
parent 449c2073a7
commit 4d4aadff66
2 changed files with 57 additions and 318 deletions
+57 -8
View File
@@ -79,14 +79,14 @@ fun iDontReturnExplicitly() {
print(iDontReturnExplicitly(), '\n'); # Prints: none
```
## Multiple returns
## Multiple return values
Functions may return more than one value:
```py
fun swap(a, b)
return b, a;
```
When a calling a function with more than one return value in an expression, only the first return value is considered.
When calling a function with more than one return value in an expression, only the first return value is considered.
```py
fun returnTwoThings()
@@ -103,7 +103,7 @@ print(first, '\n'); # Prints 5
print(second, '\n'); # Prints "I'm ignored"
```
An assignment to multiple variables is still an expression and it evaluates to the leftmost value
An assignment to multiple variables is still an expression, and it evaluates to the leftmost value
```py
fun combine(a, b)
return (a+b), (a-b);
@@ -111,11 +111,27 @@ fun combine(a, b)
print((x,y = combine(3, 4))); # Prints 7.
```
When assigning to more values than returned, the excess variables are set to none. If more values are returned than what were expected, the extra ones are ignored.
## Function arguments and default values
When a function is called with less arguments than it expected, the unprovided arguments will have the `none` value. If more arguments than what were expected are provided, the unexpected ones are ignored.
It's possible to define default values for any argument. The default value will be used when the respective argument is `none`.
```py
fun sayHello(name = "unnamed person")
print("Hello, ", name, "!\n");
sayHello("Francesc"); # Hello, Francesco!
sayHello(none); # Hello, unnamed person!
sayHello(); # Hello, unnamed person!
```
## Scoping
Functions are the only thing that creates a new scope. When variables are defined inside a function, they're relative to the function and the function only. When the function returns, the variable defined inside it are no longer available.
## Closures
When defining a function, the scope of the parent context is captured and it's always accessible by the function's body. When functions are defined in the global scope, this means that they can always access global variables
When defining a function, the scope of all the parent contexts are captured and are always accessible by the function's body. When functions are defined in the global scope, this means that they can always access global variables
```py
name = "Francesco";
@@ -130,7 +146,7 @@ name = "Giovanni";
printName(); # Prints "Giovanni"
```
But this mechanism is cooler than that. Consider the case were a function is defined inside another one
A function that's defined inside another one will be able to access both the scope of the parent function and the global scope
```py
name1 = "Giovanni";
fun outerFunc() {
@@ -142,9 +158,8 @@ fun outerFunc() {
outerFunc(); # Prints: Giovanni and Francesco are friends!
```
now the inner function can access both the variables of the outer function and the global scope.
This also applies when the function is returned
But this mechanism is cooler than that! This also applies when the function is returned
```py
name1 = "Giovanni";
fun outerFunc() {
@@ -162,4 +177,38 @@ innerFunc(); # Prints the same thing
But functions may never modify the captures scopes. Variables can only be defined relative to the local scope.
## Type hints
..
Type hints are a lightweight way to check that a function is called with the proper arguments. All of the checks are done at runtime. Each argument may be associated to one or more types. If the function is called with a type other than the specified ones, a runtime error is triggered.
```py
fun add(a: int, b: int)
return a + b;
c = add(2, 3.0); # Error!! Second argument is a float but an int was expected.
```
```py
fun add(a: int, b: int | float)
return a + b;
c = add(2, 3.0); # Now this is ok!
```
To allow a function to be `none` when using type hints, you can use the `None` type
```py
fun someFunction(optionalNumber: int | float | None) {
# ..do stuff..
}
```
Default arguments are evaluated before the type hints, therefore when `none` is provided argument, no error is triggered even when it wasn't allowed as a type if a proper default argument was provided. If the default value doesn't result in a valid type, an error is triggered.
```py
fun someFunction(a: int = 4) {}
someFunction(none); # No error. The argument value will be 4.
fun someFunction2(a: int) {}
someFunction2(none); # Error!
fun someFunction(a: int = 1.3) {}
someFunction(none); # Error!
```
-310
View File
@@ -1,310 +0,0 @@
# The Noja language
This documentation was intended for people who already program in other high level languages (such as Python, Javascript, Ruby) and don't need to be introduced to basic programming concepts like variables, expressions and branches. Not having to introduce common programming concepts leaves more space to the characteristics specific to this language.
## TL;DR
Here's an overview:
1. The available types are `int`, `float`, `bool`
`string`. `list` (array), `map` (associative array), `none` (null)
2. The `map` type can use any other type as key, even immutable ones.
3. No implicit casts.
4. Arithmetic operations only allowed on numbers.
5. Division between `int`s rounds down.
6. Logical operators expect boolean operands.
7. Assignments are expressions that return the newly assigned value.
8. No for loop (yet)
9. When calling a function, you can provide any amount of arguments and the runtime will throw the extra ones out or define the missing ones to `none`
10. Functions have closures
## Table of contents
1. [A Noja program](#a-noja-program)
2. [Expressions](#expressions)
1. [Basic values](#basic-values)
2. [Operators](#operators)
3. [Variables](#variables)
3. [If-Else statements](#if-else-statements)
4. [Loop statements](#loop-statements)
5. [Functions](#functions)
1. [Definition](#definition)
2. [Arguments](#arguments)
3. [Return value](#return-value)
4. [Closures](#closures)
## A Noja program
A Noja program is a list of statements that can be of multiple kinds. Any whitespace, if not inside strings, is ignored. Comments start with the `#` character and end with the line. There are no multi-line comments.
## Expressions
### Basic values
The most basic type of statement is an expression. They work similarly to other languages. The basic types of values are
```py
3; # Integers:
# They are represented internally by 64 bits and are always signed, therefore
# the minimum value representable is -2^63 and the maximum is 2^63-1.
3.4; # Floats
# Like integers, they are represented using 64 bits.
"hello!"; # Strings:
'hello!'; # They represent UTF-8 encoded text.
true; # Booleans:
false; # Nothing new here. They represent two values that have the property of being
# each other's logical negation.
none; # The none value:
# This is specific to the language. It can be used to represent the absence of
# a value when one was expected. It's only propery is to be identical to and
# only to itself.
[1, 2, none, false]; # Lists:
# They are ordered and etherogenic collections of values.
# Nested values can be accessed by their index relative to the
# start.
{ 1: 'hello', true: [1, 2]} # Maps:
# Maps are a list of associations between values. You can
# see them as a list of key and value pairs. The values
# are the content of the map and the keys are what you
# use to access them. Both keys and values can be any type
# of value.
```
there are also other types of values (like functions and buffers) but they'll be covered further on.
This language is stricter than others in regards to type conversions and on what types you can apply operators.
### Operators
Operators in noja genererally behave like you would expect.
Arithmetic operators can only be used on numeric values
```py
+1; # 1
-1; # -1
1 + 1; # 2
1 - 2; # -1
3 * 2; # 6
10 / 3; # 3
10 / 3.0; # 3.33..
10.0 / 3; # 3.33..
```
If the operands are integers, the result also will. If a float is involved, then the result is also a float. The only relevant thing to note here is that the division between integers results in a rounding down of the float version.
Relational operators also can only be applied on numeric types, exception made only for `==` and `!=`, which can be used to compare any type of value. These operations always result in a boolean value.
```py
1 < 2; # true
1 > 2; # false
1 >= 0; # true
1 <= 0; # false
1 == 1; # true
1 != 1; # false
```
Logical operators can only be applied on booleans and also always return booleans:
```py
not true; # false
not false; # true
true and true; # true
true and false; # false
false and true; # false
false and false; # false
true or true; # true
true or false; # true
false or true; # true
false or false; # false
not 1; # ERROR!
```
### Variables
Variable names can contain letters, digits and underscores (the first character can't be a digit though). You can define variables by assigning a value to them directly:
```py
a = 5;
```
which is similar to Python's assignment, but is a little different. In this language, assignments are considered as expressions, in fact you can do things like
```py
a = (b = 1) + 1;
# The value resulting from an assignment is the assigned value.
# After this expression, b's value is 1 and a's value is 2.
print('b = ', b, '\n'); # b = 1
print('a = ', a, '\n'); # a = 2
```
## If-Else statements
Like in every other language, it's possible to make the execution of one or more statements optional.
```py
if 1 < 2: {
print('I\'m executed!\n');
}
if 1 > 2: {
print('I\'m not..\n');
}
if 1 < 2: {
print('I\'m executed!\n');
} else {
print('I\'m not..\n');
}
```
If you only have one statement inside the if or the else branch, you can drop the curly brackets:
```py
if 1 < 2:
print('I\'m executed!\n');
if 1 > 2:
print('I\'m not..\n');
if 1 < 2:
print('I\'m executed!\n');
else
print('I\'m not..\n');
```
if-else statements don't create new variable scopes, which means variables defined inside an if-else statement's branch are defined in the parent's context. This implies that variables may or may not be defined when you access them, based on which branch is taken.
```py
# .. do stuff where you define a variable [a]..
if a < 2:
x = 100;
print(x); # May abort the execution because if [a < 2] isn't true, x would be undefined.
```
## Loop statements
Looping constructs are available in the form of while and do-while statements:
```py
i = 0;
# Iterates 10 times.
while i < 10: {
# .. do stuff ..
i = i + 1;
}
i = 0;
# Iterates 11 times.
do {
# .. do stuff ...
i = i + 1;
} while i < 10;
```
Like for if-else statements, variables defined inside the loop body are shared with the parent's context.
## Functions
### Definition
Functions can be defined using the following syntax:
```py
# Define it..
fun say_hello_to(name) {
print('Hello, ', name, '!\n\n');
}
# .. and then call it.
say_hello_to('Francesco');
```
They are actually variables, like the ones that are defined using the assignment operator. In fact, you can reassign them new values if you want.
```py
fun test_func() {
print('Hello!\n');
}
test_func = 5;
# The following line, if executed, returns an error because the test_func
# identifier is now associated to 5, which is not a function.
test_func(); # Error!!
```
### Arguments
They can have an arbitrary amount of arguments. If the function is called with more arguments than it expected, the extra values are thrown away. If the function is called with less arguments than it expected, the argument set if filled up with none values.
```py
fun test_func(a, b, c) {
print('a = ', a, '\n');
print('b = ', b, '\n');
print('c = ', c, '\n\n');
}
test_func();
# a = none
# b = none
# c = none
test_func(1, 2);
# a = 1
# b = 2
# c = none
test_func(1, 2, 3);
# a = 1
# b = 2
# c = 3
test_func(1, 2, 3, 4);
# a = 1
# b = 2
# c = 3
```
### Return value
Functions can return values exactly like in other languages:
```py
fun multiply(x, y)
return x * y;
p = 4;
q = 7;
r = multiply(p, q);
print(p, ' * ', q, ' = ', r, '\n');
```
If the function doesn't return any values, then the `none` value is returned. For example, the `print` function always returns `none`
```py
print(print()); # none
```
### Closures
Functions can also access variables that weren't defined locally but in parent scopes, but can't modify them.
```py
a = 10;
fun f() {
print(a, '\n');
}
f(); # 10
```
a common example to explain this feature is using the adder function generator
```py
fun makeAdder(n) {
fun adder(m) {
return m + n;
}
return adder;
}
add10 = makeAdder(10);
add20 = makeAdder(20);
print(add10(1), '\n'); # 11
print(add20(1), '\n'); # 21
```