Just JavaScript - Type And Non-Type
Written by Ian Elliot   
Thursday, 15 January 2015
Article Index
Just JavaScript - Type And Non-Type
Class-based type
Generics and inheritance

Class Based Type

Now it is time to move on to the much wider concept of type that you find in all strongly-typed, class-based languages. 

Class-based type is generally what people are talking about when they discuss JavaScript and its lack of type. 

We need to find out exactly what class-based typing is all about before we can look at how JavaScript copes without it. 

In a class based language declaring a class also creates a new data type.

That is: 

Class MyClassA(){
 lots of properties
}

adds the new data type MyClassA to the type system.

Now when you declare a variable of the type:

MyClassA myVariable;

the system knows what myVariable is referring to.

This allows the system to check that when you write

myVariable.myProperty 

that myProperty is indeed a property that is defined on the type. If it isn't then you get a compile time error which you can correct before it throws a run time error. 

Notice that type occurs in two ways a variable has a declared type and an object is of a particular type. 

At its simplest the strong typing simply enforces the rule that a variable can only reference and object of its declared type. 

Hierarchical Typing

Things are a little more complicated in most class based languages that implement hierarchical type.

In this case inheritance can be used to create a type hierarchy. 

For example

Class MyClassB:inherits MyClassA{
 lots of properties
}

Now MyClassB has all of the properties of MyClassA plus whatever is added as part of its definition. 

MyClassB is said to be a subclass of MyClassA and as it has all of the properties of MyClassA you can use it anywhere you could use an instance of MyClassA. 

After all typing is all about making sure that an object has the properties that you are using and an instance of MyClassB has all of the properties of MyClassA and so it can be treated as such. 

MyClassB is also a sub-type of MyClassA in the sense that it is also a MyClassA as well as being a new type all of its own.

So it is perfectly ok in most strongly typed class based languages to write things like:

MyClassA myObject=new MyClassB();

and then proceed to use any properties that belong to MyClassA. 

So the rule has now become - a variable can reference an object of its declared type or any sub-type. 

If you make a mistake and try to use a property that MyClassA doesn't have then the compiler will tell you about the error at compile time and you are saved a runtime error. 

Why is hierarchical typing useful?

It is useful because it allows you to write partially generic methods. 

For example suppose you have a class hierarchy Animal and two sub-classes Cat and Dog. As long as you only want to use methods and properties of Animal you can use hierarchical typing to write a method that can accept Animal as its parameter and use it with objects of type Animal, Cat or Dog. 

When you write a method that works with a type - it also works with all of its sub-types. 

Casts

Of course you can't use properties of the sub-type MyClassB when it is being referenced by a variable of type MyClassA. 

However there are occasions when you know that what the variable is referencing is actually a MyClassB object. 

On this situation but you can use a cast or type conversion to convert something of type MyClassA into something that is of type MyClassB - as long as of course the object really is of that type.

So for example:

MyClassA myObject=new MyClassB();

then you can only access the properties of MyClassA via myObject but if you write something like:

((MyClassB) myObject).property;

then property can be something defined as part of MyClassB. That is you have cast the myObject variable to MyClassB. 

Notice that no "type conversion" occurs. Type casting is all about changing the type of the variable not the object referenced. 

Some times a cast does produce a real type conversion but this isn't a good idea. 

If you think this all sounds complicated - it is. 

If you think this sounds all perfectly obvious it is just because you are used to it. 

Hierarchical class based typing allows you to do many things that you could do anyway if you didn't have a type system. However it is supposed to give you those facilities in a safe, usually said as "type safe" way. 

This is fine but typing doesn't mean you really are safe from runtime errors and some of them occur purely because because of the introduction of strong type.

For example if you try to cast an object to a type it isn't then you can generate a runtime error if the type isn't known and hence can't be checked at compile type.

Why do we cast?

The way that the need to cast arises is that a super-type is being used as a stand in for one of a number of possible sub-types. For example if you have an Animal class which as two sub-classes Dog and Cat then you can write a function which returns an Animal that could be either a Dog or a Cat. In any given situation you will know which it is and can cast the result to a more specific type. 

Dog myPet=(Dog) getPet();

or

Cat myPet=(Cat) getPet();

Of course this raises the question of how you know which sub-type has been returned. 

If you get it wrong the result is a runtime error and strong typing has let you down. 

Most languages provide either a test for the type returned or a way to type convert the doesn't cause a runtime error by failing silently. 

Overloading 

There are other uses for type that look as if they are not about simply checking if an object has a given property but in fact they are. 

For example function overloading can be used to pick which version of a function is called based on the signature of the function call.

That is if you define two versions of a function (or method):

function add(int x, int y){return x+y}

and

function add(float x, float y){return x+y}

then when you call add which function is called depends on the types used to call it. 

You can see that this is again a way to make sure that the code doesn't try to use properties that an object doesn't have - the right function is called to deal with the object passed. 

If you think about this for a moment it is clear that the only difference between any two overridden function is the type of the arguments. This strongly suggests that different versions are needed because of differences that come about because of the typing. In many cases function over loading is just a way of implementing generic algorithms by writing them out for each set of possible types. 

Polymorphism

Perhaps the best known additional use of type is polymorphism. 

This is often quoted as one of the pillars of object oriented programming and indeed it is but the usual meaning of polymorphism goes beyond the most basic use.

Polymorphism means that when you call a method you get the method that is defined on the object.

Well at this point you might be unimpressed as what else would you expect to happen - but things are more complicated than you might expect. 

When you call a method there are in fact two types involved in working out which method should be called - the variable's declared type and the object's actual type. 

If a variable references an object of its declared type then obviously it should call the method defined on that type. That is

MyClassA myRef=new MyClassBA();
myRef.myMethod();

calls the myMethod defined in MyClassA. What else would it call?

Now consider MyClassB a sub-class of MyClassA and:

MyClassA myRef=new MyClassB();
myRef.myMethod();

Now the variable is declared as MyClassA but it references an object of type MyClassB. 

Which myMethod should be called?

There are two possible answers. 

The call could depend only on the declared type of the variable. That is myMethod in MyClassA is used even though MyClassB has its own definition overriding it. This is often called a non-virtual method call and it has the advantage that it is determined at compile time. This makes it more efficient and this is really the only reason for using it.

The call could, and in nearly all cases should, only depend on the actual type of the object being referenced. In this case the myMethod defined in MyClassB would be used. This is often called a virtual method call and while it is more logical it is less efficient as the method call can only be resolved at run-time. 

It is the virtual method call i.e. the method of the actual object type that is called that is usually referred to as polymorphism. 

What possible use could this have?

The answer is that it saves having to test what to do because the action taken depends on the data - i.e. the type of the object.

The object has its own method that operates on it correctly. Instead of having to use an if statement to work out which function is appropriate the object uses its own "function" i.e. a method to operate correctly.

The usual example of this is a class Animal which has sub-classes Dog and Cat. 

If you want a function called makeSound that you can pass an Animal to i.e.

makeSound(Animal pet)

then you will have to include an if statement in makeSound to work out what sort of animal you have been passed and issue either a woof or a meow. 

However, if you give each sub-type its own makeSound method which produces the correct noise for the sub-type i.e. woof  for Dog and meow for Cat then you don't have to test.

If the variable animal of type Animal references either a Dog or a Cat then the call:

animal.makeSound();

will give you a  woof or a meow depending on whether animal references a Dog or a Cat object.  

If you make use of polymorphism you get the method appropriate to the data without having to test. 

What about polymorphism in JavaScript and other weakly typed languages?

In an untyped language polymorphism is the rule as variables don't have a type to use to determine the which method should be called.

The method belonging to the object referenced is always use. 

Banner



Last Updated ( Sunday, 10 May 2015 )