Member functions
Hybrix classes can be used for object-oriented programming (OOP), a classic software engineering paradigm. It is a relatively deep topic, but the basic idea is simple: you add func members to your class, and then invoke them using the . operator. For example, similar to accessing a door.color member variable, we might call a door.open() member function.
The different kinds of member functions are explained below.
Func members​
Let's start with a basic example. The function below has a hidden parameter self that allows access to the current instance of the object.
class rectangle
var width: int
var height: int
func get_area(): int
return self.width * self.height
end func
end class
module main
func start()
var r: rectangle
new rectangle() -> r
# Accessing member variables
10 -> r.width
20 -> r.height
var a: int
# Calling a member function
r.get_area() -> a
end func
end module
Important points:
- The
funcmembers must appear after anyvarmembers. For example,var width: intmust come beforefunc get_area(): int. - The
.operator is used to call a member function liker.get_area(), just like how we would user.widthto access a member variable. - Inside that
func, the specialselfvariable returns the class instance. In our example,selfrefers to the same object asr. - Instead of writing
self.widthorself.get_area(), you can simply write.widthor.get_area()for short.
Here's how class rectangle looks when using . instead of self.:
# (same meaning as the above definition)
class rectangle
var width: int
var height: int
func get_area(): int
return .width * .height
end func
end class
Constructors​
A class constructor is a special member function that is called automatically when using new to create an object. The constructor initializes the new object's member variables.
In the previous section we defined a circle class like this:
class circle
var x: int, y: int
var radius: int
end class
module main
func start()
var c1: circle, c2: circle
new circle() -> c1
0 -> c1.x
0 -> c1.y
5 -> c1.radius
new circle() -> c2
10 -> c2.x
20 -> c2.y
5 -> c2.radius
end func
end module
The statements such as 0 -> c1.x are tedious to write, and it's easy to accidentally forget one of them. Here's an equivalent program that uses a class constructor:
class circle
var x: int, y: int
var radius: int
# The class constructor:
constructor(x: int, y: int, radius: int)
x -> .x
y -> .y
radius -> .radius
end constructor
end class
module main
func start()
var c1: circle, c2: circle
# Create a new circle, invoking its constructor
new circle(0, 0, 5) -> c1
# Create a new circle, invoking its constructor
new circle(10, 20, 5) -> c2
end func
end module
Important points:
- Class constructors are defined using
constructor. - Class constructors are called automatically by expressions such as
new circle(0, 0, 5) - Behind the scenes, the
newexpression calls an allocator function to allocate heap memory for the object, then the constructor is called for that object. - It might seem that a constructor's return value should be
circle, but the constructor itself doesn't return anything. It receives the partially initialized object as itsselfparameter. - Note that when we write
(new circle)to make a function pointer, its type isfunc(): circle. This is because it actually points to the allocator, not the constructor function. (Otherwise, how can the constructor use.xto reference theselfobject?) - Constructors are optional; if omitted, the compiler provides a default constructor with no function parameters.
Hook members​
Classes also support another kind of function defined using hook instead of func. They behave almost exactly the same as func. The difference has to do with inheritance, which we'll discuss in the next section.
Indexers​
If a class acts like a container of elements, it can be more intuitive to use an array-like notation to refer to its elements. The classic examples are list[i] (a resizable wrapper for an array) and map[key] <- value (a dictionary where values are looked up using a key). For example, a statement like my_map.set(key1, my_map.get(key2)) can be more intuitive as my_map[key1] <- my_map[key2].
The Hybrix language supports this syntax using indexers, declared using the special forms func get[](...) and func set[](...). In the example below, clamped_array behaves basically like an array, except it guarantees that the array elements are always within the range -100 to 100:
class clamped_array
var _items: int[]
# ("view var" prevents other classes from modifying "size")
view var size: int
constructor(size: int)
._items <- new int[](size)
.size <- size
end constructor
func get[](i: int): int
return ._items[i]
end func
func set[](i: int, value: int)
if value < -100 then
-100 -> value
elsif value > 100 then
100 -> value
end if
._items[i] <- value
end func
end class
module main
func start()
var a: clamped_array
a <- new clamped_array(5)
a[0] <- 50 # stores 50
a[1] <- 1234 # stores 100
a[2] <- -1234 # stores -100
end func
end module
Important points:
- Reading from
my_object[x]is equivalent to callingmy_object.get(x). - Assigning
my_object[x] <- yis equivalent to callingmy_object.set(x, y). - The
[]is not part of the function name; it merely enables the indexer syntax. - The
getindexer must receive exactly one parameter and return a value. - The
setindexer must receive exactly two parameters with no return value. - Both indexers are optional, but if you define
setthen you must also defineget. - The index and value parameters can be any data type, but they must be consistent between
getandsetindexers. - In all other respects, indexers are normal member functions. For example, you can write
(my_object.get)to make a function pointer. - The Hybrix language does not provide general operator overloading; indexers are the only user-defined operator-like syntax.