Methods¶
Methods are reusable blocks of code that can be applied to its arguments.
Methods are similar to mathematical functions, although they are not strictly the same thing. In other languages, Imba methods are known as 'functions', 'procedures', and 'subroutines'. While these things may have slightly different meanings to different programmers, they all serve the same purpose.
Methods are used to reduce duplication where similar or identical behavior is used in different parts of a program. They are identified by a name, parameters that serve as placeholders for arguments passed to them, and the method body, which is the code that belongs to a method.
The best way to think of methods is 'configurable named code blocks'. They can be configured using parameters, and they are packaged into a named method so you can easily refer to it by name without retyping the entire block again and again.
Cheats¶
- Syntax for defining methods is
def methodName x, y, z - Methods in module scope are literally methods, not stand-alone functions.
- Methods in module scope are methods on a
selfobject which is an object in the private module scope. - Methods can reference each other in the module via the
selfofthisobjects. - The rest spread
...argsis written as*args. - Optional arguments can be given a default value with
arg = expsyntax. - Default values of optional arguments apply even when
undefinedis passed explicitly. - Parenthesis around arguments are optional even when there are no arguments.
Basic syntax¶
Here is a simple method that has no parameters.
1 2 | def completeDenial
no
|
To use this method we call it. Since the method completeDenial has no
arguments it can be called simply by referencing its name in the code:
1 | completeDenial |
If you have programmed in another language, you may think that this is what's
commonly known as a 'function', but in Imba, these are real methods. They are
methods on an object called self.
We can call the completeDenial method like this as well:
1 | self.completeDenial |
Method parameters¶
As mentioned at the beginning of this section, methods can have parameters. Parameters are used to configure how a method operates or pass it values to operate on. Here is a method that takes two parameters.
1 2 | def add x, y
x + y
|
To call the method add, we reference its name, and we also supply values to
it by listing the values after the name of the function.
1 2 | add 1, 2 # 3 |
The values that were given (or 'passed') to add are also called
'arguments', so we can say that 'we passed arguments 1 and 2 to add.'
The vales 1 and 2 are bound to names x and y in the function. The
code of the method body is then evaluated, and the value of the last
expression (x + y) is returned.
Method return value¶
Methods have a return value. The return value of a method is the last
statement in the method. In the case of completeDenial, the return value is
no as it is the last (and only) expression that evaluated. This is called
an 'implicit return'.
The return statement can be used to explicitly return a value. Here is an
example of a function that returns a value explicitly.
1 2 3 4 5 6 7 | var ammo = 100
var cheatsEnabled = yes
def shoot
if cheatsEnabled
return ammo
ammo -= 1
|
In the above method, we have two statements, the if block and ammo. Since
the return value is always the last statement, we have to use return to
short-circuit the function if we want to return from within the if block.
Alternatively, we can make the if block the last statement by including the else clause. This gets rid of the return statement.
1 2 3 4 5 6 7 8 | var ammo = 100
var cheatsEnabled = yes
def shoot
if cheatsEnabled
ammo
else
ammo -= 1
|
In the section about control structures, we have briefly
talked about using return to short-circuit a loop inside methods. When
using a return statement inside a loop, the method returns immediately,
just like with if blocks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var battery = {
capacity: 200
charge: 15
}
def charge batt, amount
while amount
batt:charge += 1
if batt:charge > batt:capacity
return "Blown!"
"Charged"
charge battery, 500
# "Blown!"
|
It's important to keep in mind what happens if we short-circuit return from a loop when loop is the last statement in a method. For example:
1 2 3 4 5 6 7 8 9 10 11 12 | def brokenRange x
var i = 1
while i < x
if (i > 50)
return i
i
brokenRange 100
# 51
brokenRange 5
# [1, 2, 3, 4]
|
Optional parameters¶
One or more parameters in a method can be made optional. This is done by assigning a default value to a parameter.
1 2 | def bang text, puncutation = '!'
text + punctuation
|
The parameter punctuation will have a default value of '!' if no values are
bound to it, or if the value bound to it is undefined (more on this special
value later).
1 2 | var excessiveGreating = bang 'Hello' var normalGreeting = bang 'Hello', '.' |
In the example, excessiveGreeting will be 'Hello!', because the second
value is omitted.
Destructured parameters¶
When objects are passed to methods, they can be destructured with default values. This is done by writing one or more key-value pairs in place of a parameter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def heal player, intensity: 5
player:hp += intensity
var player = {
hp: 10
mp: 200
dmg: 500
}
var potion = {
intensity: 40
}
heal player, potion
player:hp
# 50
|
Parentheses around key-value pairs are optional.
The default value is used when a key is not found on the object.
1 2 3 | heal player, {}
player:hp
# 55
|
If a non-object is passed for a destructured parameter, it will be treated as the default value is used for the destructured keys.
1 2 3 | heal player, null player:hp # 60 |
Callback parameter¶
Methods can declare their last parameter explicitly as a callback using the
& prefix.
1 2 3 4 5 | def saveGame filename, overwrite = no, &callback
if overwrite is no and fileExists filename
callback 'error'
writeFile filename, gameState
callback()
|
With the callback parameter defined with the & parameter, when a do
block or a method is passed to saveGame, it will know to bind the
last argument correctly to the callback parameter even if overwrite is
skipped.
1 2 3 | saveGame '001.sav', do |error|
if error
showError
|
Without the & prefix, the second argument would be treated as the
overwrite argument.
This callback parameter only has an effect when there are optional parameters.
Rest parameters¶
Methods can be declared with rest parameters which capture any parameters
that are not explicitly declared. The rest parameter is declared using a *
followed by the name given to an array of captured parameters.
1 2 3 4 5 | def equip player, *weapons
for weapon in weapons
player.equal weapon
equip player, 'sword', 'laser gun', 'onion'
|
Referencing methods as objects¶
Just like any other value in JavaScript, methods are also objects. They can be assigned and passed to other methods and functions as arguments.
We already mentioned that methods, when referenced by name, are immediately
called. When we want to use them as values instead of calling them, we need to
prefix the method name with self:.
1 2 3 4 5 | def increment x
x + 1
[1, 2, 3].map self:increment
# [2, 3, 4]
|
Declaring methods in object literals¶
Methods can be declared inside object literals.
1 2 3 4 5 6 | var laserGun = {
ammo: 1000
dmg: 50
def shoot
"Woosh!"
}
|
This creates a property on the object that has a key matching the method name.
Methods defined in object literals can be called using the object.key
notation.
1 2 | laserGun.shoot # "Woosh!" |
When defined inside object literals, we can use self to refer to the object
to which the method belongs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var laserGun = {
ammo: 1000
dmg: 50
def shoot target
self:ammo -= 1
target:hp -= self:dmg
}
var badPerson = {
hp: 100
}
laserGun.shoot badPerson
badPerson:hp
# 50
|
When we want to use the method as a value, we can reference it using the :
operator.
1 | var shoot = laserGun:shoot |
In Imba, all methods are unbound. This means that they are not hard-wired to
the objects to which they belong. To demonstrate this we will use the shoot
variable we have defined earlier to shoot another target.
1 2 3 4 5 6 | var anotherTarget = {
hp: 50
}
shoot anotherTarget
# TypeError: this is undefined
|
What happened is that shoot is unbound, so self is undefined. Attempting
to decrease the value of self:ammo threw a TypeError exception.
This is the result of what is know as 'invocation context'. Methods are
called as bound only when we specify the object on which they are called
using object.key notation. In the last example, we did not use the .
anywhere so the method was called unbound (it's invocation context was
undefined).
Note
Also read about .bind,
.call and .apply
methods for different ways to control the invocation context.
Declaring methods within methods¶
Methods can be declared anywhere, including inside other methods. However, declaring a method within another method can be a bit tricky to wrap one's head around.
The important thing to remember is that a method is declared as a property on
self. Consider this example.
1 2 3 4 5 6 | def outer
def inner
'Hello from inner'
var x = outer
var y = inner
|
The outer method, when called, defines the inner method. Since
def inner is the last statement in outer, that is also its return value.
Therefore, x is the inner method. Inside the outer method, self is
the same self on which outer itself is defined. Because of this, inner
is also defined on the same self. In other words, self now looks like
this:
1 2 3 4 5 6 7 8 | var self = {
def outer
def inner
console.log 'Hello from inner'
def inner
console.log 'Hello from inner'
}
|
The way to think about this is that outer is a method that creates the
inner.
We are now able to call inner just by referencing inner, so the value of
y is 'Hello from inner'.
If we take this one step further, it means that outer can also redefine
inner if it is already defined. Let's take a look at a modified example:
1 2 3 4 5 6 7 8 9 | def inner
'I was here first'
def outer
def inner
'No, I was here first!'
outer
var whoWasFirst = inner
|
The value of whoWasFirst is 'No, I was here first!'.
Method type¶
All methods are objects of type 'function'.
Useful methods and properties¶
Note
It may sound strange, but methods have their own methods and properties, because they are themselves objects.