When to use Type Inference in Haxe

Unlike AS3, Haxe has a feature called type inference that allows you to forgo specifying the types of variables or functions. Instead, Haxe will try and determine the types of variables for you if you leave them unspecified. At first, I was hesitant about taking advantage of this feature since it made me feel more organized when everything was labeled. But, after having used the language for a month, I've come to realize that type inference can really simply your code without losing any information.

Here is an example of the power of type inference. In AS3, every variable required an explicit type in order for the compiler to be happy:

var x: int = 7;
var s: String = "seven";

In Haxe, though, you do not need to tell it what they types of x and s are (although, you are certainly more than welcome to):

var x = 7;
var s = "seven";

Here, x is still an Int, and s is a String. AS3 will actually let you do this also, but it makes both variables of type Object, not their real types! Haxe is smart enough to look at what you set the variable equal to, and from there it decides what the type of the variable is. Since 7 is an integer, x must be an Int.

Type inference also works for function parameters and return types. In fact, only class fields really require type names to be given for the compiler to work, so if you really wanted to, you could let Haxe's type inference do all the work for you. However, abusing this feature can really quickly lead to extremely unreadable code, because even if the program knows the types of variables, a human reading the code would not.

So, when should I use type inference, and when should I type variables and functions manually?

My personal rule of thumb is this: If the type is obvious from context or too complicated to be practical, use type inference. If it is not obvious, specify the type.

For example, the types of all the below variables are very obvious, so why would you need to specify a type?

var n = 7;            // an Int
var s = "seven";      // a String
var x = 7.237;        // a Float, because of the decimal
var car = new Car();  // the constructor tells us what it is

The below examples have type names that may be complicated to write out. Furthermore, you may find that you don't really need to know these types yourself, as you only need to know how to use the variable:

var f = function(a: Array<Int>, start: Int, end: Int): Array<Int> { };
// f is a Array<Int>->Int->Int->Array<Int>.  Do you want to write all that?
 
var grid = makeNumberCube();
// probably an Array<Array<Array<Int>>>

Given that, though, there are still some places where I still would prefer to always manually type my fields. Function parameters, for instance, should be typed unless a default value is provided. Haxe is still able to infer the types of the parameters on its own, but leaving out the types dramatically reduces the readability of the function. As the typenames are integral to how a function is used, it is good practice to continue writing them.

//  Do the rgb values go from 0-255, or from 0.0-1.0?
function rgbToHex(r, g, b): String { }
//  Now it is obvious from the type that it goes from 0-255
function rgbToHex(r: Int, g: Int, b: Int): String { }

The same is true for a function's return type.

Then, there are at least two places where types must be specified. The first of these is in class field names. If a class's field is not initialized, then there is no way for the compiler to be able to infer a field's type. Therefore, you must specify it:

class Ellipse{
    public var semimajor(default, null): Float; // good!
    public var semiminor(default, null); // bad!  needs type.
 
    public static var nextId(default, null) = 0; // ok, since initialized
}

The second case for which you need to specify a type if the compiler is guaranteed to infer a type you do not intend. It sounds like this shouldn't happen, but when working with inheritance, it can happen often. Say for instance that you have a Shape class. Both Triangle and Circle extend Shape. Now, let's say that you are making an array of shapes, but you initially fill the array with just Triangles. Later, you attempt to add a Circle:

var t1 = new Triangle();
var t2 = new Triangle();
var c1 = new Circle();
 
var a = [t1, t2];
 
a.push(c1);

This will actually throw a compiler error. Why? Simply, a is inferred to be an Array<Triangle>, but we're trying to add a Circle! In this case, you must manually specify that a is an Array<Shape>.

Hopefully this guide helps you understand when it is safe for you to use type inference. Ultimately, the policy is up to you or your team as to what works best.