Bi-directional Type Inference
TL;DR: It`s bi-directional: provide the necessary type information for generics wherever you want to, as long as it is not ambiguous.
One of Swift`s best features is well hidden.
Actually, the less code you write, the more it takes control.
Let`s talk about type inference.
Let`s talk about bi-directional type inference.
Strongly typed meant to be a lot of typing ⌨️
When you take a look at the Tiobe Index the PL ranking 1st is Java (April 2016). And I guess quite a lot of people are already familiar with the Java way of generics and types.
First of all, pretty much everything you get from Java generics should work in Swift too. But Swift generics are way more powerful and allow concepts that are impossible to do in a Java type system.
Example? Here comes Java:
To be honest - it`s not fun to write the Java examples as there is too much stuff to type. And the worst part - after providing all the information - at the end the generic types will be erased. E.g. if you look at
numbers.getClass();it is of type
class java.util.ArrayList. You can run the code online in the Groovy console.
For the next example I switch to Groovy which is like Java, but friendlier and more concise.
Damn. I mean… What?
Exactly - this is the way type inference works in many mainstream languages.
- The type information flows only from the leaf of the expression to the root. In that case
[1,2,3,4]provides the type information (List of Integers) to
- Flowing in the opposite direction is not possible. The root
List<Double> doublescannot be inferred to the leaf
Next Stop: Leaf of expression 🍀
Back to Swift. To demonstrate the so-called bi-directional type inference we begin with an easy example.
- defining a list of Int (or is it a list of Double?)
Awesome! The Type Information flows to the leaf of your expression.
Beware: Type information can not be inferred across multiple expressions!
We can even try to build a more complex expression:
Why is it working?
- The compiler infers
Intdepending on context.
0are FloatLiteralConvertibles and can initialize a Float
So far we only applied inference to built-in generic types like
Optional and the
But knowing that type information flows in an expression let us build flexible constructs ourselves.
BEWARE: There is a thin line between solving generics and warnings like:
Expression was too complex ....
Write once, infer anywhere 🍵
In the next step we write a generic function and make use of type inference to solve the generics.
Goal: We write a generic function, which takes a value and returns a function to check if an optional value of the same type is less than the provided value.
Let`s invoke it:
All you need to take care of is that the provided type identifiers work nicely together. We deconstruct Version 3:
- 10 can represent
DoublePredicaterepresents a function of type
(Double?) -> Bool
lessThan<T: Comparable>(tx: T) -> T? -> Boolwants that
thave the same Type (and
When we apply these constraints to the func we get something like:
And now? 💭
With these concepts in mind, what can we build up … ?
As an example I want to take a look at Palau, a framework we published on github. It provides easy typesafe
NSUserDefaults access. The whole concept of Palau works only because of bi-directional type inference.
The framework is based on generics and protocols with associated types. Only by defining the return type of a computed property we infer the types for the actual implementation throughout the framework.
Pseudocode not working in playground
Generics IN your NSUserDefaults 🍢
As we kept Palau generic to the bone, you can even go one step further and create NSUserDefaults of type
FloatLiteralConvertible. But we have no generic constants yet - so we need a function.
And now the generic usage:
The same NSUserDefaults value can be treated as
CGFloat. This is kinda cool.
Types FTW 🏁
Understanding type inference is an important part in order to build reusable code parts that increase productivity and type safety. So type inference and the idea that a type can provide all information in order to be stored in the defaults was the idea behind Palau.
- In the first time, try to get a feeling when it`s appropriate to provide no type identifier at all.
- When you start with generics, try type inference from the root.
- Then start to mix all kind of type inference and start to smile when reading “Expression was to complex…”.
- If you want to: work through the Palau Sourcode and see it in action.
The gist of it 👻
👻 Feel free to hit me on twitter. @elmkretzer