5. Generic Parameters – Static typing without typing
Typing system
The functional core of F# is statically typed, which means all checks concerning typing are done during compile time. Both Statically typed and dynamically typed system have pros and cons. Dynamic typing allows flexibility while static typing allows safeness and speed. Then a lot of programming languages try to get the benefits of both worlds.
A lot of OOP languages uses both dynamic and static typing. So It is the case of course for C# but also for F# which allows imperative language style OOP to ensure a perfect interoperability with the classes and other languages of the .NET Framework.
Compilers for these languages try to optimize and check the code as much as possible, so they favor static typing and allow dynamic typing only when necessary. The good thing about F# is that if you write functional code thanks to generic parameters that I am going to introduce in this article and don’t lay on the imperative style OOP system you don’t need dynamic typing to get flexibility !
The imperative way
Let’s see how C# before its 2.0 version worked with polymorphism to resolve the flexibility versus safeness and speed dilemma (before C# 2.0 generics didn’t exist). Below are the types of polymorphism it supported:
- Inclusion
- Overloading
- Coercion
Example of C# code using those types of polymorphism:
Description:
1) The IList Add method takes an object as a first argument. Since the String class extends from Object you can use a string, this is a static upcast.
2) We convert 1582 which is by default an int to an uint this operation is not safe, but can be statically done since the number is a constant.
3) We use a virtual method on an object, a dynamic link is necessary to find the method implementation to use.
4) The Console class has several overloads for the static method Write. We choose the one taking an object at compile time, this is static.
5) In this case of coercion we are doing a downcast to convert an instance of Object to an instance of the UInt32 class, this is dynamic.
Even if C# could have used dynamic polymorphism all the time, the compiler limited its use to certain types of inclusion and coercion polymorphisms for performance and safeness reasons. This is the reason behind the use of the virtual and override keywords in C#, the developer has to tell the compiler when to make dynamic links for inclusion polymorphism to avoid doing it all the time like in Java. And it also justifies the as keyword which allows you to do quicker static downcast.
From C# v2.0 a new type of polymorphism appeared thanks to the addition of generics in the language: the parametric polymorphism. And guess what? Don Syme the designer and architect of F# worked on it. The advantage of parametric polymorphism is that it allows you a lot of flexibility dynamic typing does but in a statically typed manner, so you get safeness and speed for free.
The functional way
When you writes a function with F#, the compilers tries to infer types for the input(s) and the output if it can’t it gives by default a generic type, represented by variable parameters called generic parameters. Generic parameters are prefixed by a single quote.
What if you want to build the identity function in C# (without generics)?
Knowing all objects extends from Object you could do:
But later at some point in your program you would need to make downcast to operate on the value, so using some dynamic behaviors.
With F# your function is by default generic and everything is static:
This function type is ‘a –> ‘a where ‘a can be int, string, obj, …
F# allows you to build optimized and checked generic operations without any effort !
The simple example above shows you how you can define a function which represents the generic concept of repeating an action three times whatever the action is.
The practical way
Some generic concepts are really pure, it is the case of the identity function. It works for all input you could give it and you don’t need to define specialized implementation.
As long as you play with generic parameters and generic values your algorithm stay generics. As soon as you introduce literal values or non generic functions in your function definition, it becomes tainted, and types are inferred at the expense of genericity.
Sometimes you may believe that primitive functions such as the mathematics operators (+), (-), (*), (/), (%) … use generic parameters because they work for different types and give you some flexibility, but they are using method overloading. The reason is simple, they need to be implemented at the machine level specifying different implementations. If (+) was generic and that you would have used it to add functions what would be its behavior?
Example of how to make a non generic number algorithm generic: