Type Systems
Describes the rules and behaviors for types in a language/system.
Rules
Rules which should be outlined by a type system.
Types Definition
Defining custom types.
Literal Type
A literal type is a value, enforced as a type.
That means, a variable of literal type can only have value of the literal and nothing else.
You can say it is a subtype of a primitive data type. While primitive data types allow a range of values, literal narrows it down to specific predefined value (values in some cases).
For example:
let iamlit: "Hello"; //"Hello" is literal type
iamlit = "Hello"; //OK
iamlit = "Bounjor" //Error
Here iamlit
can only have value of the literal.
Enum
(Enumerated Types)
Data type consisting of a set of named values called elements, members, enumeral, or enumerators of the type. The enumerator names are usually identifiers that behave as constants in the language.
Usage
Where fixed set of constants or ranges are required
Examples
- the four suits in a deck of playing cards may be four enumerators named Club, Diamond, Heart, and Spade, belonging to an enumerated type named suit. If a variable V is declared having suit as its data type, one can assign any of those four values to it.
- range of phones/laptops, colors, seasons, days of week.
Isn't it #Literal Type?
While enums, like literal limit the set of values possible for a variable, they are not literal. Enums group together #Literal Types and give them semantic meaning.
Union Types
Combines several types into one.
Represents value that can be of any of the constituents types.
Example:
let id: number | string
id
can be either number
or string
.
Intersection Types
Combines several types into one.
Represents value that can be both of the constituents types.
Type Aliasing
Providing custom names for existing types.
Type Polymorphism
Ability of a programming language to write code which can work for multiple types.
- Parametric Polymorphism (Generic Programming)
- Ad-hoc Polymorphism (Function Overloading/Operator Overloading)
- Subtype Polymorphism (Inheritance/Interfaces)
Casting / Type Conversion
Casting is the conversion of variable/object from one data type to another.
Depending upon the entity it is being performed on or the way it is being performed it can be classified as follows:
Classification based on casting syntax
Explicit Casting
- When you have to specify that you're converting the variable/object intentionally and you don't have any problem with loosing information in your variable or making your object more specific.
- #Narrowing Conversion and #Downcasting.
Implicit Casting
- When you don't need to specify that you need some variable/object to be converted. The language takes care of it automatically.
- #Widening Conversion and #Upcasting.
- aka Implicit Type Conversion
- aka Implicit Type Coercion, used in slightly different context. "coercion" is often used when the automatic change happens in more implicit, possibly lossy ways, especially in dynamic languages.
Classification based on entity being casted
Primitive Casting
When conversion is performed on primitive data types.
Narrowing Conversion
- Conversion from larger data type to smaller.
- Generally, #Explicit Casting.
Widening Conversion
- When we convert from a smaller/simpler data type to a larger one.
- Generally, #Implicit Casting.
Object Type Casting
-
Similarity with primitive conversion => Converting from one type to another
-
Difference
Primitive type variables store values. So when we convert from a larger to type to smaller type, we might end up loosing information.
Reference variables on the other hand do not contain the object itself, but its reference. So when we convert types of objects, we're not changing the object, but we're just changing the label on the object, expanding or narrowing the opportunities to work with.
Upcasting narrows the list of methods and properties available to this object and down casting can extend it.
Upcasting
Casting from subclass to superclass
Syntax
Generally, #Implicit Casting. Why? => Compiler knows that Cat is an Animal. OO Design Principles#Liskov Substitution Principle
What upcasting gives us?
-
Instead of using different methods for a common functionality in each of the sub classes, we can have a common method in the superclass for that functionality and all subclass objects will be casted implicitly.
Example - a feed(Animal animal) function to which we can pass objects of Cat and Dog classes → feed(cat); feed(dog);
-
When an object is upcasted, it still can call overridden methods from its original class.
For example, if Animal class has an eat() method and subclasses Cat and Dog override it.
eat(animal) will call the methods from either the class Cat or Dog, whichever it was before upcasting.
OOP#Runtime Polymorphism is a result of Upcasting.
Downcasting
Casting from superclass to subclass
Syntax
Generally, #Explicit Casting and same as #Narrowing Conversion of primitive types.
Beware of Issues#Class Cast Exception.
Characteristics
Type Safety
Defines what kind of conversions are allowed, more specifically in case of incompatible types. For example, int
to string
.
Strong Typing
Strongly-typed systems don't allow #Implicit Casting of incompatible types. You have to explicitly state that you want to convert the type.
Weak Typing
Weak-types systems try to convert different types on its own. You don't have to specify when assigning a value of one type to another incompatible type.
Type Inference
Ability of a system to infer types without them being explicitly annotated.
Type Checking
Defines the stage at which the types are validated.
Static Type Checking
Types checked at compile time.
Dynamic Type Checking
Types checked at runtime.
Type Compatibility
Nominal Typing (Name-based Typing)
Two types are only same/compatible if they share common name or inheritance hierarchy.
Structural Typing (Structure-based Typing)
Two types are compatible if they share same structure (properties and methods), regardless of their names.
Type Relations
Subclassing
Parent's code can be shared by subclasses.
Substitutability
We can replace an object of Animal type with object of Cat type.
More formally — OO Design Principles#Liskov Substitution Principle.
Subtyping
A set of classes confirming to a certain interface signature which can override certain parts of the signature with their own implementations.
Variance
Variance defines how the type relations of complex types will vary or hold in their components( entities and components in programming languages like enumerables (enums), functions, arrays, lists etc).
We know how #Substitutability works in case of objects. This kind of replacement can also be done with the components of these types. This is called variance.
- When objects are replaced — substitutability
- When components are replaced — variance
For example, a type Cat and Animal, where Animal is parent (or whatever) of Cat. Variance defines how the subtypes of Cat and subtypes of Animal will either hold or deviate from this relation.
The relation is:
- variant if the relation is somehow carried on to the components or children, either holding the relation or reversing the relation.
- covariant if it preserves the ordering of types.
- contravariant if it reverses this order. ^105062
- bivariant if both of the above apply
- invariant or nonvariant if the relation is not carried on to the components or children. ^b36f21
Thoughts on deciding variance
While designing any type system, variance is considered by the designer.
- Contravariance is usually considered unintuitive by programmers. This can lead to complex typing rules.
- If the type system is variant, the type system is considered to be well-typed.
- Sometimes the designer will choose to keep it invariant to keep the type system simple. But this could voilate type-safety.