JavaScript Type Inference

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
m (Text replace - "<javascript>" to "<syntaxhighlight lang="javascript">")
m (Text replace - "</javascript>" to "</syntaxhighlight>")
 
Line 22: Line 22:
 
var s1 = "foo"; // declare new variable s1
 
var s1 = "foo"; // declare new variable s1
 
var s2 = s1.Replace("f", "b"); // s1 is a string so Replace is cool
 
var s2 = s1.Replace("f", "b"); // s1 is a string so Replace is cool
</javascript>
+
</syntaxhighlight>
  
 
Only the first assignment to a variable is taken into account by the type inference mechanism.
 
Only the first assignment to a variable is taken into account by the type inference mechanism.
Line 30: Line 30:
 
var s = "I'm a string"; // s is bound with type string
 
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
 
s = 42; // and although 42 is a really cool number s can only hold strings
</javascript>
+
</syntaxhighlight>
  
 
=== Fields ===
 
=== Fields ===
Line 39: Line 39:
 
   var _name = "";
 
   var _name = "";
 
}
 
}
</javascript>
+
</syntaxhighlight>
  
 
Declare the new field _name and initialize it with an empty string. The type of the field will be string.
 
Declare the new field _name and initialize it with an empty string. The type of the field will be string.
Line 54: Line 54:
  
 
var c = ["foo", 2]; // c is of type object[]
 
var c = ["foo", 2]; // c is of type object[]
</javascript>
+
</syntaxhighlight>
  
 
=== For statement variables ===
 
=== For statement variables ===
Line 72: Line 72:
 
   Debug.Log(name.Trim()); // Trim is cool, name is a string
 
   Debug.Log(name.Trim()); // Trim is cool, name is a string
 
}
 
}
</javascript>
+
</syntaxhighlight>
  
 
This works even when with *unpacking*:
 
This works even when with *unpacking*:
Line 82: Line 82:
 
     Debug.Log(i+j); // + is cool since i and j are typed int
 
     Debug.Log(i+j); // + is cool since i and j are typed int
 
}
 
}
</javascript>
+
</syntaxhighlight>
  
 
=== Overriden methods ===
 
=== Overriden methods ===
Line 94: Line 94:
 
   override function ToString() {}
 
   override function ToString() {}
 
}
 
}
</javascript>
+
</syntaxhighlight>
  
 
=== Method return types ===
 
=== Method return types ===
Line 107: Line 107:
 
// multiply operator is cool since spam() is inferred to return a string
 
// multiply operator is cool since spam() is inferred to return a string
 
// and strings can be multiplied by integer values
 
// and strings can be multiplied by integer values
</javascript>
+
</syntaxhighlight>
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
Line 117: Line 117:
 
// ltuae is inferred to return object
 
// 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  
 
Debug.Log(ltuae(false)*3); // This is slow, as the * operator is looked up on run time due to duck typing  
</javascript>
+
</syntaxhighlight>
  
 
When a method does not declare a return type and includes no return statements it will be typed System.Void.
 
When a method does not declare a return type and includes no return statements it will be typed System.Void.
Line 139: Line 139:
  
 
e = EvenThoseTears();
 
e = EvenThoseTears();
</javascript>
+
</syntaxhighlight>
  
 
=== (i) Ok. So where I do have to declare types then? ===
 
=== (i) Ok. So where I do have to declare types then? ===
Line 153: Line 153:
 
   print(i*2);
 
   print(i*2);
 
}
 
}
</javascript>
+
</syntaxhighlight>
 
'''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.'''
 
'''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.'''
  
Line 170: Line 170:
 
     a = "42"; // uh, oh
 
     a = "42"; // uh, oh
 
}
 
}
</javascript>
+
</syntaxhighlight>
  
 
* When you want to access a member not exposed by the type assigned to an expression without resorting to dynamic typing:
 
* When you want to access a member not exposed by the type assigned to an expression without resorting to dynamic typing:
Line 176: Line 176:
 
var f : IDisposable = foo();
 
var f : IDisposable = foo();
 
f.Dispose();
 
f.Dispose();
</javascript>
+
</syntaxhighlight>
  
 
=== Dynamic Type Checking ===
 
=== Dynamic Type Checking ===
Line 190: Line 190:
 
var pa = GetComponent(ParticleAnimator);
 
var pa = GetComponent(ParticleAnimator);
 
pa.wroldRotationAxis = Vector3.up;
 
pa.wroldRotationAxis = Vector3.up;
</javascript>
+
</syntaxhighlight>
 
: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.
 
: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.
  
Line 201: Line 201:
 
d.something();
 
d.something();
 
d.anything=2;
 
d.anything=2;
</javascript>
+
</syntaxhighlight>
  
 
Even though this seems odd and counter intuitive, this is useful for a lot of dynamic tricks. See [[:Category:IQuackFu]] for more information.
 
Even though this seems odd and counter intuitive, this is useful for a lot of dynamic tricks. See [[:Category:IQuackFu]] for more information.

Latest revision as of 20:52, 10 January 2012

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

[edit] 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

[edit] 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.

[edit] 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[]

[edit] 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
}

[edit] 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() {}
}

[edit] 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.

[edit] (!) 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();

[edit] (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();

[edit] 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