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
self
object which is an object in the private module scope. - Methods can reference each other in the module via the
self
ofthis
objects. - The rest spread
...args
is written as*args
. - Optional arguments can be given a default value with
arg = exp
syntax. - Default values of optional arguments apply even when
undefined
is 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.