haXe 2: The Dynamic Type and Properties

0
112
7 min read

 

haXe 2 Beginner’s Guide

haXe 2 Beginner's Guide

Develop exciting applications with this multi-platform programming language

        Read more about this book      

(For more resources on this subject, see here.)

Freeing yourself from the typing system

The goal of the Dynamic type is to allow one to free oneself from the typing system. In fact, when you define a variable as being a Dynamic type, this means that the compiler won’t make any kind of type checking on this variable.

Time for action – Assigning to Dynamic variables

When you declare a variable as being Dynamic, you will be able to assign any value to it at compile time. So you can actually compile this code:

class DynamicTest
{
public static function main()
{
var dynamicVar : Dynamic;
dynamicVar = "Hello";
dynamicVar = 123;
dynamicVar = {name:"John", lastName : "Doe"};
dynamicVar = new Array<String>();
}
}

The compiler won’t mind even though you are assigning values with different types to the same variable!

Time for action – Assigning from Dynamic variables

You can assign the content of any Dynamic variable to a variable of any type. Indeed, we generally say that the Dynamic type can be used in place of any type and that a variable of type Dynamic is indeed of any type.

So, with that in mind, you can now see that you can write and compile this code:

class DynamicTest
{
public static function main()
{
var dynamicVar : Dynamic;
var year : Int;
dynamicVar = "Hello";
year = dynamicVar;
}
}

So, here, even though we will indeed assign a String to a variable typed as Int, the compiler won’t complain. But you should keep in mind that this is only at compile time! If you abuse this possibility, you may get some strange behavior!

Field access

A Dynamic variable has an infinite number of fields all of Dynamic type. That means you can write the following:

class DynamicTest
{
public static function main()
{
var dynamicVar : Dynamic;
dynamicVar = {};
dynamicVar.age = 12; //age is Dynamic
dynamicVar.name = "Benjamin"; //name is Dynamic
}
}

Note that whether this code will work or not at runtime is highly dependent on the runtime you’re targeting.

Functions in Dynamic variables

It is also possible to store functions in Dynamic variables and to call them:

class DynamicTest
{
public static function main()
{
var dynamicVar : Dynamic;
dynamicVar = function (name : String) { trace("Hello" + name); };
dynamicVar();

var dynamicVar2 : Dynamic = {};
dynamicVar2.sayBye = function (name : String) { trace("Bye" +
name ); };
dynamicVar2.sayBye();
}
}

As you can see, it is possible to assign functions to a Dynamic variable or even to one of its fields. It’s then possible to call them as you would do with any function.

Again, even though this code will compile, its success at running will depend on your target.

Parameterized Dynamic class

You can parameterize the Dynamic class to slightly modify its behavior. When parameterized, every field of a Dynamic variable will be of the given type.

Let’s see an example:

class DynamicTest
{
public static function main()
{
var dynamicVar : Dynamic<String>;
dynamicVar = {};
dynamicVar.name = "Benjamin"; //name is a String
dynamicVar.age = 12; //Won't compile since age is a String
}
}

In this example, dynamicVar.name and dynamicVar.age are of type String, therefore, this example will fail to compile on line 7 because we are trying to assign an Int to a String.

Classes implementing Dynamic

A class can implement a Dynamic, parameterized or not.

Time for action – Implementing a non-parameterized Dynamic

When one implements a non-parameterized Dynamic in a class, one will be able to access an infinite number of fields in an instance. All fields that are not declared in the class will be of type Dynamic.

So, for example:

class User implements Dynamic
{
public var name : String;
public var age : Int;
//...
}
//...
var u = new User(); //u is of type User
u.name = "Benjamin"; //String
u.age = 22; //Int
u.functionrole = "Author"; //Dynamic

What just happened?

As you can see, the functionrole field is not declared in the User class, so it is of type Dynamic.

In fact, when you try to access a field that’s not declared in the class, a function named resolve will be called and it will get the name of the property accessed. You can then return the value you want. This can be very useful to implement some magic things.

Time for action – Implementing a parameterized Dynamic

When implementing a parameterized Dynamic, you will get the same behavior as with a non-parameterized Dynamic except that the fields that are not declared in the class will be of the type given as a parameter.

Let’s take almost the same example but with a parameterized Dynamic:

class User implements Dynamic<String>
{
public var name : String;
public var age : Int;
//...
}
//...
var u = new User(); //u is of type User
u.name = "Benjamin"; //String
u.age = 22; //Int
u.functionrole = "Author"; //String because of the type parameter

What just happened?

As you can see here, fields that are not declared in the class are of type String because we gave String as a type parameter.

Using a resolve function when implementing Dynamic

Now we are going to use what we’ve just learned. We are going to implement a Component class that will be instantiated from a configuration file.

A component will have properties and metadata. Such properties and metadata are not pre-determined, which means that the properties’ names and values will be read from the configuration file.

Each line of the configuration file will hold the name of the property or metadata, its value, and a 0 if it’s a property (or otherwise it will be a metadata). Each of these fields will be separated by a space.

The last constraint is that we should be able to read the value of a property or metadata by using the dot-notation.

Time for action – Writing our Component class

As you may have guessed, we will begin with a very simple Component class — all it has to do at first is to have two Hashes: one for metadata, the other one for properties.

class Component
{
public var properties : Hash<String>;
public var metadata : Hash<String>;

public function new()
{
properties = new Hash<String>();
metadata = new Hash<String>();
}
}

It is that simple at the moment.

As you can see, we do not implement access via the dot-notation at the moment. We will do it later, but the class won’t be very complicated even with the support for this notation.

Time for action – Parsing the configuration file

We are now going to parse our configuration file to create a new instance of the Component class.

In order to do that, we are going to create a ComponentParser class. It will contain two functions:

  • parseConfigurationFile to parse a configuration file and return an instance of Component.
  • writeConfigurationFile that will take an instance of Component and write data to a file.

Let’s see how our class should look at the moment (this example will only work on neko):

class ComponentParser
{
/**
* This function takes a path to a configuration file and returns
an instance of ComponentParser
*/
public static function parseConfigurationFile(path : String)
{
var stream = neko.io.File.read(path, false); //Open our file for
reading in character mode
var comp = new Component(); //Create a new instance of Component
while(!stream.eof()) //While we're not at the end of the file
{
var str = stream.readLine(); //Read one line from file
var fields = str.split(" "); //Split the string using space
as delimiter
if(fields[2] == "0")
{
comp.properties.set(fields[0], fields[1]); //Set the
key<->value in the properties Hash
} else
{
comp.metadata.set(fields[0], fields[1]); //Set the
key<->value in the metadata Hash
}
}
stream.close();
return comp;
}
}

It’s not that complicated, and you would actually use the same kind of method if you were going to use a XML file.

Time for action – Testing our parser

Before continuing any further, we should test our parser in order to be sure that it works as expected.

To do this, we can use the following configuration file:

nameMyComponent 1
textHelloWorld 0

If everything works as expected, we should have a name metadata with the value MyComponent and a property named text with the value HelloWorld.

Let’s write a simple test class:

class ComponentImpl
{
public static function main(): Void
{
var comp = ComponentParser.parseConfigurationFile("conf.txt");
trace(comp.properties.get("text"));
trace(comp.metadata.get("name"));
}
}

Now, if everything went well, while running this program, you should get the following output:

ComponentImpl.hx:6: HelloWorld
ComponentImpl.hx:7: MyComponent

LEAVE A REPLY

Please enter your comment!
Please enter your name here