JavaScript Type Inference

From Unify Community Wiki
Jump to: navigation, search

Note: This page is based on a page on the Boo wiki about Type Inference

Unity's JavaScript can be used as a statically typed language.

Static typing is about the ability to type check a program for type correctness.

Static typing is about being able to deliver better runtime performance.

Static typing is not about putting the burden of declaring types on the programmer as most mainstream languages do.

The mechanism that frees the programmer from having to babysit the compiler is called type inference.

Type inference means you don't have to worry about declaring types everywhere just to make the compiler happy. Type inference means you can be productive without giving up the safety net of the type system nor sacrificing performance.

The type inference kicks in for newly declared variables and fields, properties, arrays, for statement variables, overriden methods, method return types and generators.

Contents

Variables

The var keyword is used to create new variables in the current scope. When assigning an initial value, the type for the new variable will be inferred from the expression on the right.

var s1 = "foo"; // declare new variable s1
var s2 = s1.Replace("f", "b"); // s1 is a string so Replace is cool

Only the first assignment to a variable is taken into account by the type inference mechanism. The following program is illegal:

var s = "I'm a string"; // s is bound with type string
s = 42; // and although 42 is a really cool number s can only hold strings

Fields

class Customer {
 
   var _name = "";
}

Declare the new field _name and initialize it with an empty string. The type of the field will be string.

Arrays

Note: this does probably not apply to javascript as the [] create lists and not arrays...

The type of an array is inferred as the least generic type that could safely hold all of its enclosing elements.

var a = [1, 2]; // a is of type int[]
 
var b = [1L, 2]; // b is of type long[]
 
var c = ["foo", 2]; // c is of type object[]

For statement variables

Note: this does probably not apply to javascript as the [] create lists and not arrays...

var names = [" John ",
" Eric",
" Graham",
"TerryG ",
" TerryJ",
" Michael"];
 
for (var name in names) {
   // name is typed string since we are iterating a string array
   Debug.Log(name.Trim()); // Trim is cool, name is a string
}

This works even when with *unpacking*:

var a = [ [1, 2], [3, 4] ];
 
for (var i, j in a) {
    Debug.Log(i+j); // + is cool since i and j are typed int
}

Overriden methods

When overriding a method is not necessary to declare its return type since it can be safely inferred from its super method.

Note: the current version of Unity JavaScript does not implement the override keyword

class Customer extends Person {
   override function ToString() {}
}

Method return types

The return type of a method will the most generic type among the types of the expressions used in return statements.

function spam() {
    return "spam!";
}
Debug.Log(spam()*3);
// multiply operator is cool since spam() is inferred to return a string
// and strings can be multiplied by integer values
function ltuae(returnString : bool) {
    return "42" if returnString;
    return 42;
}
 
// ltuae is inferred to return object
Debug.Log(ltuae(false)*3); // This is slow, as the * operator is looked up on run time due to duck typing

When a method does not declare a return type and includes no return statements it will be typed System.Void.

(!) A Word of Caution About Interfaces

When implementing interfaces it's important to explicitliy declare the signature of a method, property or event. The compiler will look only for exact matches.

In the example below the class will be considered abstract since it does not provide an implementation with the correct signature:

interface IMeMineIMeMineIMeMine {
    function AllThroughTheNight(iMeMine, iMeMine, iMeMine : int);
}
 
class EvenThoseTears imlpements IMeMineIMeMineIMeMine {
    function AllThroughTheNight(iMeMine, iMeMine, iMeMine) {
    }
}
 
e = EvenThoseTears();

(i) Ok. So where I do have to declare types then?

Let's say when.

  • When the compiler as it exists today can't do it for you. Ex: parameter types, recursive and mutually recursive method/property/field definitions, return for abstract and interface methods, for in untyped containers, properties with a only set defined
function Method(param /* : object */, i : int) : string {
}
 
for i : int in [1, 2, 3] { // list is not typed
  print(i*2);
}

The above code will still run without the explicit types in the examples above, but it will revert to dynamic type checking, which is slower.

  • When you don't want to express what the compiler thinks you do:
function foo() : object { // I want the return type to be object not string
    // a common scenario is interface implementation
    return "a string";
}
 
var a;
if (bar)  {
    a = 3; // a will be typed int
}
else {
    a = "42"; // uh, oh
}
  • When you want to access a member not exposed by the type assigned to an expression without resorting to dynamic typing:
var f : IDisposable = foo();
f.Dispose();

Dynamic Type Checking

Sometimes the type inference does not have enough information to infer the correct type. In that case the type will be set to object and all further type checking on the variable will be done in runtime as opposed to compile time. This is also called duck typing. An example of where type inference fails is the return value of GetComponent. As the type of the return value depends on the input parameter, it is not possible to know beforehand what type it will be.

There are two reasons why one wants to avoid dynamic type checking:

  • Performance: Run-time type checking takes a bit of processing time, expecially in code that gets run over and over. Statically typed variables (manually typed or through type inference) take a bit less time to run.
  • Better error reporting: Say you have the following code:
var pa = GetComponent(ParticleAnimator);
pa.wroldRotationAxis = Vector3.up;
Despite the fact that "wroldRotationAxis" is meaningless, Unity will not know this until this code actually gets executed at runtime. If pa had been specifically typed as a ParticleEmitter, the compiler would tell you that wroldRotationAxis was not a member of ParticleEmitter.
This is doubly important with code that gets run only sparingly, such as end-of-level code. If your level takes 5 minutes to complete, and there is a typo in your end-of-level script, then not statically typing your variable will have just cost you at least 5 minutes.

One can also explicitly enable dynamic type checking, or duck typing by setting the variable type to object:

var d : object;
//...
d.something();
d.anything=2;

Even though this seems odd and counter intuitive, this is useful for a lot of dynamic tricks. See Category:IQuackFu for more information.

Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox