Types, functions, and errors ​
Identifiers and Type ​
Function Signatures (Delegates) ​
delegate DelegateType(a:int)A delegate is a type of function pointer, allowing chunks of code to be passed around like objects. The example above defines a delegate called DelegateType which represents a function taking an int and not returning a value. Any function with such a signature may be used as a delegate as shown in the following sample:
[indent=4]
delegate DelegateType (a : int) : bool
def f1 (a:int) : bool
print "testing delegate value %d", a
return true
def f2 (d:DelegateType, a:int)
var c = d (a)
init
f2 (f1, 5)This code will execute the function f2, passing in a pointer to function f1 and the number 5. f2 will then execute the function f1, passing it the number.
Delegates may also be created locally. A member method can also be assigned to a delegate:
class Test : Object
data : int = 5
def method (a:int)
print "%d %d", a, data
delegate DelegateType (a : int)
init
var t = new Test()
d : DelegateType = t.method
d(1)Using a delegate within a class is quite similar to its usage in a namespace. It is important, however, to utilize the constructor to avoid assertion self != null failed run-time errors:
class Test:Object
delegate DelegateType (a : int)
data : int = 5
d : DelegateType
construct()
self.d = method
def method( b:int )
print "%d %d", b, data
def run ( c:int )
d( c )
init
var t = new Test()
t.run( 1 )Classes ​
Classes allow new data types to be defined. When a class is instantiated it is called an object. Classes are covered in detail in the Objects section below.
Interfaces ​
A class in Genie may implement any number of interfaces. Each interface is a type, much like a class, but one that cannot be instantiated. By implementing one or more interfaces, a class may declare that its instances are also instances of the interface, and therefore may be used in any situation where an instance of that interface is expected.
interface Test
prop abstract data:int
def abstract fn ()A possible implementation of this interface is:
init
var f = new Foo()
f.fn()
interface Test
prop abstract data:int
def abstract fn()
class Foo:Object implements Test
prop data:int
def fn()
print "fn"Interfaces in Genie may also inherit from other interfaces:
interface List : CollectionA class wishing to implement List must also describe all implemented interfaces:
class ListCLass : Object implements Collection, ListType Inference ​
Genie has a mechanism called Type Inference, whereby a local variable may be defined using var instead of giving a type, so long as it is unambiguous what type is meant:
var i = 3
var
a = "happy"
b = "sad"
c = "ambivalent"
for var I = 1 to 10
print "looping"var can be used for type inferencing in both single lines and blocks as well as for statement initializers.
Parameters of Type (Generics) ​
Genie includes a runtime generics system, by which a particular instance of a class can be restricted with a particular type or set of types chosen at construction time:
[indent=4]
init
var string_wrapper = new Wrapper of string
string_wrapper.set_data ("Hello World")
var s = string_wrapper.get_data ()
print s
var int_wrapper = new Wrapper of int
int_wrapper.set_data (6)
var data = int_wrapper.get_data ()
print "test int is %d", data
var test_wrapper = new Wrapper of TestClass
var test = new TestClass
test.accept_object_wrapper (test_wrapper)
class Wrapper of G : Object
_data : G
def set_data (data : G)
_data = data
def get_data () : G
return _data
class TestClass : Object
def accept_object_wrapper (w : Wrapper of Object)
print "accepted object"In Genie, generics are handled while the program is running. When you define a class that can be restricted by a type, there still exists only one class, with each instance customised individually. This is in contrast to C++ which creates a new class for each type restriction required - Genie's system is similar to the one used by Java.
Functions ​
Defining a Named Function ​
A block of Genie code can be labelled with an identifier. The identifier can then be used to call the block at other points in the program without needing to copy the original code. In Genie a function definition starts with the def keyword, followed by some optional function definition modifiers, and then the identifier:
def example_function()
print( "example_function has been called" )To call a function from another part of your program, place parentheses () after the function identifier:
init
example_function()
def example_function()
print( "example_function has been called" )This example will output:
example_function has been calledA function can also be assigned to a variable. Genie is a strongly typed language and a type must be given to the variable. Types for functions are declared with the delegate keyword:
delegate FunctionExample()In the following example the FunctionExample type is used to declare the call_me variable. First the example_function is assigned to call_me. Note that example_function does not have parentheses after it; this means it is not the result of the function itself that is assigned to the variable, but the identifier of the function:
init
example_function()
call_me:FunctionExample = example_function
call_me()
call_me = example_procedure
call_me()
delegate FunctionExample()
def example_function()
print( "example_function called" )
def example_procedure()
print( "example_procedure called" )Parameters ​
A function in Genie is passed zero or more parameters. The default behaviour when a function is called is as follows:
- Any value type parameters are copied to a location local to the function as it executes.
- Any reference type parameters are not copied, instead just a reference to them is passed to the function.
This behaviour can be changed with the modifiers ref and out.
outfrom the caller side: you may pass an uninitialized variable to the method and you may expect it to be initialized after the method returns.outfrom the callee side: the parameter is considered uninitialized and you have to initialize it.reffrom the caller side: the variable you're passing to the method has to be initialized and it may or may not be changed by the method.reffrom the callee side: the parameter is considered initialized and you may change it or not.
Here's an example:
[indent=4]
init
var
a = 1
b = 2
c = 3
Foo.bar (a, out b, ref c)
print "a=%d, b=%d, c=%d", a,b,c
class Foo : Object
def static bar (a:int, out b: int, ref c: int)
a = 10
b = 20
c = 30The treatment of each variable will be:
ais of a value type. The value will be copied into a new memory location local to the function, and so changes to it will not be visible to the caller.bis also of a value type, but passed as anoutparameter. In this case, the value is not copied, instead a pointer to the data is passed to the function, and so any change to the function parameter will be visible to the calling code.cis treated in the same way asb, the only change is in the signalled intent of the function.
try...except Blocks ​
GLib has a system for managing runtime exceptions called GError. Genie translates this into a form familiar to modern programming languages, but its implementation means it is not quite the same as in Java or C#. It is important to consider when to use this type of error handling: GError is specifically designed to deal with recoverable runtime errors that are not known until the program runs on a live system. You should not use GError for problems that can be foreseen, such as reporting that an invalid value has been passed to a function.
Using exceptions is a matter of:
Declaring that a function may raise an error:
geniedef fn (s:string) raises IOErrorThrowing the error when appropriate:
genieif not check_file (s) raise new IOError.FILE_NOT_FOUND ("Requested file could not be found.")Catching the error from the calling code:
genietry fn("home/jamie/test") except ex : IOError print "Error: %s", ex.message
Errors have three components: "domain", "code" and "message". Error domains describe the type of problem, and equate to a subclass of Exception in Java. The third part, the error code, is a refinement describing the exact variety of problem encountered.
The way to define this information about error types is related to the implementation in GLib. In order for the examples here to work, a definition is needed such as:
exception IOError
FILE_NOT_FOUND
FILE_NO_READ_PERMISSION
FILE_IS_LOCKEDA finally block can be placed after try and any except blocks to be run always at the end of the section, regardless of whether an error was thrown or any handlers were executed:
[indent=4]
exception ErrorType1
CODE_1A
exception ErrorType2
CODE_2A
init
try
Test.catcher ()
except ex:ErrorType1
print ex.message + " was caught"
class Test:Object
def static thrower () raises ErrorType1, ErrorType2
raise new ErrorType2.CODE_2A( "Error CODE 2A was raised" )
def static catcher () raises ErrorType1
try
thrower ()
except ex:ErrorType2
print ex.message
finally
print "all exceptions handled"