Method
Methods are recipes that can take arguments to "fill in the blanks".
in FatScript, we refer to functions as Methods, irrespective of their definition context
Definition
A method is anonymously defined with a thin arrow ->
, like so:
<parameters> -> <recipe>
Parameters can be omitted if none are needed:
-> <recipe> # arity zero
To register a method to the scope, assign it to an identifier:
<identifier> = <parameters> -> <recipe>
Parameters loaded into a method's execution scope are immutable, ensuring that the method's operations do not alter their original state. For mutable behavior, consider passing a scope or utilizing a custom type capable of encapsulating multiple values and states.
Optional parameters
While method signatures typically require a fixed number of mandatory parameters, FatScript supports optional parameters through default values:
greet = (name: Text = 'World') -> {
'Hello, {name}'
}
greet() # 'Hello, World'
In this example, the name
parameter is optional, defaulting to 'World' if no argument is provided. This feature allows for more flexible method invocations.
Implicit argument
A convenience offered by FatScript is the ability to reference a value passed to the method without explicitly specifying a name for it. In this case, the implicit argument is represented by the underscore _
.
Here's an example that illustrates the use of implicit argument:
double = -> _ * 2
double(3) # output: 6
You can use an implicit argument whenever you need to perform a simple operation on a single parameter without assigning a specific name to it, but note that the method must have arity zero to trigger it.
Arguments handling
In FatScript, while there is support for optional parameters and implicit argument, any other extra arguments are simply ignored to enhance both flexibility and performance.
The design decision to ignore extra arguments also means there is no native support for variable-length arguments in the traditional sense. To achieve similar functionality, you may declare optional parameters like so:
vaMethod = (v1 = null, v2 = null, v3 = null, v4 = null) -> ...
keep in mind that you need to explicitly list each parameter you want to capture and defining a very large number of parameters (e.g., more than 10) may reduce method call performance
Auto-return
FatScript uses auto-return, meaning the last standing value is returned:
answer: Method = (theGreatQuestion) -> {
# TODO: explain Life, the Universe and Everything
42
}
answer('6 x 7 = ?') # outputs: 42
Return type safety
In FatScript, one peculiarity is that even when you declare a method with a specific return type, the language allows for null
values, like in:
fn = (arg: Text): Text -> ... ? ... : null
This means that while the method is declared to return Text
, the return value is, in a sense, optional because the method can also return Void
. The only strict guarantee is that if the method tries to return an incompatible type, such as a Number
or Boolean
, a TypeError
will be raised. This design choice introduces implicit flexibility while still maintaining a degree of type safety.
If you need to ensure a non-null outcome, you can wrap your call with Option like this:
Option(fn(myArg)).getOrElse('fallbackVal')
Procedures
FatScript introduces a unique feature that simplifies method calls, when no arguments are involved.
The <>
symbol declares a Procedure, an argument-free function that executes automatically when referenced:
<identifier> = <type> <> <recipe>
for procedure syntax the
type
needs to be a single word; if you need a composite type, declare it as an alias beforehand and use the aliaspassing arguments to a procedure will result in an error, as procedures do not accept arguments
Key benefits:
Reduced boilerplate: Reduces the need for parentheses, making code cleaner and more concise, for zero-parameter procedures that act like properties.
Dynamic computation: Allows for dynamic computation with outputs that can change based on the object's internal or global state.
Deferred execution: Enables deferred execution, useful in asynchronous programming and complex initialization patterns.
starting in version
4.0.0
, only procedures support automatic execution without parentheses; classic zero-arity methods are no longer executed automatically and require parentheses()
for execution
Avoiding an automatic call
To reference a procedure without triggering the automatic calling feature, you can use the get syntax:
foo('bar') # yields a reference to foo.bar, without calling it
FatScript also offers self
and root
keywords to reference procedures at the local and global levels, respectively:
self('myLocalProcedure')
root('myGlobalProcedure')
The tilde ~
also operator allows you to bypass the automatic call feature, providing flexibility in procedure handling:
# Both lines below fetch the procedure reference, without calling it
foo.~bar
~ myProcedure
Argument labels
FatScript supports argument labels, which allow you to specify names for arguments at the call site. These labels improve code readability and self-documentation by making the intent of each argument explicit:
# Defining a method with parameters
fn = (a: Number, b: Number) -> a + b
# Calling the method with argument labels
fn(a = 1, b = 2) # output: 3, same as fn(1, 2)
If provided, labels are validated against the method's parameter names. Arguments must be passed in the same order as defined in the method signature. Using incorrect labels will raise an error:
fn(b = 1, a = 2) # CallError: invalid name 'b' at pos: 1
arguments are resolved sequentially, not by the labels; therefore, out-of-order resolution is not allowed, even when labels are used
Contrast with type instantiation
While argument labels in method calls are mostly decorative, they play a functional role in type instantiation. When creating instances of types, argument labels are matched by name to the type's properties, allowing out-of-order resolution.
By maintaining sequential resolution for methods, FatScript ensures better performance in method calls, while type instantiation benefits from the flexibility of named argument resolution.
Tail Recursion Optimization
FatScript supports Tail Recursion Optimization (TRO) to enhance performance by conserving stack space. To benefit from this optimization, several conditions must be satisfied:
Explicit parameters: Methods must explicitly declare parameters; the implicit argument feature is not supported for TRO.
Flow control: TRO is only compatible with
If-Else
,Cases
, andSwitch
constructs for branching.Call structure: Nested method calls, such as
x(a)(b)(c)
, are not supported for TRO.Recursive calls: The method must call itself recursively by name as the final operation in its execution path.
For example, a function correctly set up for TRO might look like this:
tailRec = (n: Number, m: Number): Void -> {
n > m => console.log('done')
_ => {
console.log(n)
tailRec(n + 1, m)
}
}
In this example, tailRec
recursively calls itself as the final operation in one of the branches, making it eligible for optimization.
You can check if TRO has been enabled for your method using static analysis with the fry --probe
option.
TRO can be disabled by wrapping the recursive call within parentheses, as shown below:
...
(tailRec(n + 1, m)) # no TRO