Functions
Overview¶
Functions provide a powerful way to make your scripts more modular, reusable, and easier to maintain. By defining functions that can be used in both tasks and dialogs, you can write more concise and flexible scripts for managing conversations and computations.Functions can accept parameters, perform calculations or operations, and return a result. These functions can be used in both tasks and dialogs to enhance the flexibility of your scripts.
Basic Syntax¶
A Bubblescript function is defined using the following syntax:
function function_name(_parameter1, _parameter2, ...) do
# function body
# do some work here
return result
end
function_name
: The name of the function that will be used to call it.- Parameters: Local variables that are passed to the function. These must be prefixed with an underscore (
_
) and are separated by commas. do
: Marks the start of the function's body.return
: Returns the result of the function.end
: Ends the function definition.
Some key points to notice are:
- Functions can return any value, including numbers, strings, or even other variables.
- Functions can be called inside tasks or dialogs to process data or execute specific logic, or from other functions.
- Functions cannot be named the same as any of the builtin functions, e.g. creating a function called
date_format
will return an error.
Function guards¶
Functions can be defined multiple times with guard clauses. A guard clause allows you to specify conditions under which a particular function definition will be executed. This feature enables more flexible and dynamic handling of different inputs or cases within a function.
A function with a guard clause is defined similarly to a regular function, but with an additional when
clause. The when
clause specifies the condition that must be true for that particular version of the function to be executed:
function function_name(parameter1, parameter2, ...) when condition do
# function body
end
when
clause: This is where you specify the condition for the function to run.- Condition: Any valid expression that evaluates to a boolean value (
true
orfalse
).
If no guard clause condition is met, the default version of the function (the one without a when
clause) will be executed.
In the following example, we define a function foo
that behaves differently based on the value of the parameter _x
.
function foo(_x) when _x < 3 do
return "Input is less than 3"
end
function foo(_x) do
return "Input is 3 or greater"
end
dialog check_input do
say foo(2) # Calls the first version of foo
say foo(5) # Calls the second version of foo
end
In this case, when foo(2)
is called, the guard clause _x < 3
is
satisfied, so the first version of foo
is executed, returning
"Input is less than 3"
. When foo(5)
is called, the guard clause is
not satisfied, so the second version of foo
is executed, returning
"Input is 3 or greater"
.
Using Functions in Tasks¶
Tasks are used to define operations that happen behind the scenes. Functions can be called within tasks to compute values or trigger processes.
Example 1: Basic Addition¶
function sum(_a, _b) do
return _a + _b
end
task calculate_sum do
result = sum(10, 5) # function call inside a task
end
In the above example:
- A function
sum
is defined, which takes two parameters and returns their sum. - Inside the task
calculate_sum
, thesum
function is called with the arguments10
and5
, and the result is stored in theresult
variable.
Example 2: Concatenating Strings¶
function concatenate(_first, _second) do
return _first + " " + _second
end
task combine_names do
full_name = concatenate("John", "Doe") # combines first and last name
end
In this example:
- The
concatenate
function joins two strings with a space in between. - The task
combine_names
calls the function to combine the first name "John" and the last name "Doe".
Using Functions in Dialogs¶
Dialogs are used to manage interactions with users. You can call functions inside dialogs to compute values and display them dynamically during conversations.
Example 1: Displaying Calculated Values¶
function multiply(_x, _y) do
return _x * _y
end
dialog main do
say "The result of multiplication is: " + multiply(7, 3)
end
In this example:
- The
multiply
function takes two numbers and returns their product. - Inside the dialog
main
, themultiply
function is called, and the result is displayed in the conversation usingsay
.
Example 2: Greeting the User¶
function greet(_name) do
return "Hello, " + _name + "!"
end
dialog welcome_user do
user_name = "Alice"
say greet(user_name)
end
Here:
- The
greet
function returns a personalized greeting. - Inside the
welcome_user
dialog, the function is called to greet the user by name.
Best Practices¶
Some points to consider when writing functions:
- Use Descriptive Function Names: Choose function names that describe what the function does, such as
sum
,concatenate
, orgreet
, to make your code more readable. - Reusability: Use functions to encapsulate repetitive logic and avoid redundancy in your tasks and dialogs.
- Parameter Naming: Always prefix parameters with an underscore (
_
) to differentiate them from other variables. - Return Meaningful Values: Ensure that your functions return values that are useful for your tasks or dialogs.
Runtime Type Checking¶
Functions in Bubblescript support runtime type checking through type annotations. This feature helps ensure that functions receive and return values of the expected types, making your scripts more robust and easier to debug.
Runtime type checking causes some performance overhead when used, because it needs to check the types of all arguments and return values at runtime. Use sparingly.
Basic Type Annotations¶
Type annotations can be added to function parameters and return values using the ::
syntax:
function get_price(_user :: @schemas.user, _multiplier :: %{type: "number"}) :: %{type: "number"} do
return _user.base_price * _multiplier
end
In this example:
_user :: @schemas.user
specifies that the_user
parameter must be a user schema_multiplier :: %{type: "number"}
indicates that_multiplier
must be a number:: %{type: "number"}
after the parameter list specifies that the function must return a number
Combining Types with Guards and Descriptions¶
Type annotations can be combined with guards:
function get_price(_user :: @schemas.user, _multiplier :: %{type: "number"}) :: %{type: "number"},
when _multiplier >= 1 do
return _user.base_price * _multiplier
end
Type checking works across multiple function clauses - each clause's parameters will be checked against their type annotations at runtime.
Available Types¶
Common type annotations include:
- Basic types:
%{type: "string"}
,%{type: "number"}
,%{type: "boolean"}
- Schema types as defined in
schemas
files:@schemas.my_user
, etc. - Custom schemas:
%{type: "object", properties: {...}}
Type checking happens at runtime, providing immediate feedback if a function receives arguments of incorrect types.
When a typecheck fails, the bot process crashes with a fatal(...)
error; which can be caught by an __error__
dialog, when needed.