aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-08-19 15:57:06 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-08-19 15:57:06 -0400
commitd4bde89b5c0293312a6922dc8e801b7810eab50b (patch)
tree0b492f58c1bc4eb0585af4556220af047f96acce
parent61f9cfa12c544cc671e1f8f683077295965ddbea (diff)
Document functions
-rw-r--r--README.md2
-rw-r--r--docs/README.md3
-rw-r--r--docs/functions.md175
3 files changed, 178 insertions, 2 deletions
diff --git a/README.md b/README.md
index 2d72924b..6b686410 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ language features.
### Performance
- Extremely high performance code generation with minimal overhead compared to C
- Extremely fast parallel [compilation](docs/compilation.md) times
-- Language-level support for correct function caching
+- Language-level support for correct [function caching](docs/functions.md)
- [Structs](docs/structs.md) with known-at-compile-time methods, not OOP
objects with vtable lookups
diff --git a/docs/README.md b/docs/README.md
index 5bb35de9..2f0dabb0 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -7,10 +7,11 @@ This is an overview of the documentation on Tomo.
A few topics that are documented:
- [Compilation Pipeline](compilation.md)
+- [Functions](functions.md)
- [Libraries/Modules](libraries.md)
-- [Special Methods](metamethods.md)
- [Namespacing](namespacing.md)
- [Operator Overloading](operators.md)
+- [Special Methods](metamethods.md)
## Types
diff --git a/docs/functions.md b/docs/functions.md
new file mode 100644
index 00000000..61a10037
--- /dev/null
+++ b/docs/functions.md
@@ -0,0 +1,175 @@
+# Functions and Lambdas
+
+In Tomo, you can define functions with the `func` keyword:
+
+```tomo
+func add(x:Int, y:Int)->Int:
+ return x + y
+```
+
+Functions require you to explicitly write out the types of each argument and
+the return type of the function (unless the function doesn't return any values).
+
+For convenience, you can lump arguments with the same type together to avoid
+having to retype the same type name: `func add(x, y:Int)->Int`
+
+## Default Arguments
+
+Instead of giving a type, you can provide a default argument and the type
+checker will infer the type of the argument from that value:
+
+```tomo
+func increment(x:Int, amount=1)->Int:
+ return x + amount
+```
+
+Default arguments are used to fill in arguments that were not provided at the
+callsite:
+
+```tomo
+>> increment(5)
+= 6
+
+>> increment(5, 10)
+= 15
+```
+
+**Note:** Default arguments are re-evaluated at the callsite for each function
+call, so if your default argument is `func foo(x=Int.random(1,10))->Int`, then
+each time you call the function without an `x` argument, it will give you a new
+random number.
+
+## Keyword Arguments
+
+Tomo supports calling functions using keyword arguments to specify the values
+of any argument. Keyword arguments can be at any position in the function call
+and are bound to arguments first, followed by binding positional arguments to
+any unbound arguments, in order:
+
+```tomo
+func foo(x:Int, y:Text, z:Num):
+ return "x=$x y=$y z=$z"
+
+>> foo(x=1, y="hi", z=2.5)
+= "x=1 y=hi z=2.5"
+
+>> foo(z=2.5, 1, "hi")
+= "x=1 y=hi z=2.5"
+```
+
+As an implementation detail, all function calls are compiled to normal
+positional argument passing, the compiler just does the work to determine which
+order the arguments will be placed. Arguments are _evaluated_ in the order in
+which they appear in code.
+
+## Function Caching
+
+Tomo supports automatic function caching using the `cached` or `cache_size=N`
+attributes on a function definition:
+
+```tomo
+func add(x, y:Int; cached)->Int:
+ return x + y
+```
+
+Cached functions are outwardly identical to uncached functions, but internally,
+they maintain a table that maps a struct containing the input arguments to the
+return value for those arguments. The above example is functionally similar to
+the following code:
+
+```tomo
+func _add(x, y:Int)->Int:
+ return x + y
+
+struct add_args(x,y:Int)
+add_cache := @{:add_args:Int}
+
+func add(x, y:Int)->Int:
+ args := add_args(x, y)
+ if add_cache:has(args):
+ return add_cache:get(args)
+ ret := _add(x, y)
+ add_cache:set(args, ret)
+ return ret
+```
+
+You can also set a maximum cache size, which causes a random cache entry to be
+evicted if the cache has reached the maximum size and needs to insert a new
+entry:
+
+```tomo
+func doop(x:Int, y:Text, z:[Int]; cache_size=100)->Text:
+ return "x=$x y=$y z=$z"
+```
+
+## Inline Functions
+
+Functions can also be given an `inline` attribute, which encourages the
+compiler to inline the function when possible:
+
+```tomo
+func add(x, y:Int; inline)->Int:
+ return x + y
+```
+
+This will directly translate to putting the `inline` keyword on the function in
+the transpiled C code.
+
+# Lambdas
+
+In Tomo, you can define lambda functions, also known as anonymous functions, like
+this:
+
+```tomo
+fn := func(x,y:Int): x + y
+```
+
+The normal form of a lambda is to give a return expression after the colon,
+but you can also use a block that includes statements:
+
+```tomo
+fn := func(x,y:Int):
+ if x == 0:
+ return y
+ return x + y
+```
+
+Lambda functions must declare the types of their arguments, but do not require
+declaring the return type. Because lambdas cannot be recursive or corecursive
+(since they aren't declared with a name), it is always possible to infer the
+return type without much difficulty.
+
+## Closures
+
+When declaring a lambda function, any variables that are referenced from the
+enclosing scope will be implicitly copied into a heap-allocated userdata
+structure and attached to the lambda so that it can continue to reference those
+values. **Captured values are copied to a new location at the moment the lambda
+is created and will not reflect changes to local variables.**
+
+```tomo
+func create_adder(n:Int) -> func(i:Int)->Int:
+ adder := func(i:Int):
+ return n + i
+
+ n = -1 // This does not affect the adder
+ return adder
+...
+add10 := create_adder(10)
+>> add10(5)
+= 15
+```
+
+Under the hood, all user functions that are passed around in Tomo are passed as
+a struct with two members: a function pointer and a pointer to any captured
+values. When compiling the lambda to a function in C, we implicitly add a
+`userdata` parameter and access fields on that structure when we need to access
+variables from the closure. Captured variables _can_ be modified by the lambda
+function, but those changes will only be visible to that particular lambda
+function.
+
+**Note:** if a captured value is a pointer to a value that lives in heap
+memory, the pointer is copied, not the value in heap memory. This means that
+you can have a lambda that captures a reference to a mutable object on the heap
+and can modify that object. However, lambdas are not allowed to capture stack
+pointers and the compiler will give you an error if you attempt to do so.