It's Friday and today we have another great article written by Stéphane Derosiaux on Types: Never commit too early, happy reading!
'Types: Never commit too early
A few days ago, I was answering to a tweet from @kubukoz (https://twitter.com/sderosiaux/status/1027653596404436993):
“Or F[_]: Sync. Never commit too early. >:-)”
This leads me to think a bit more about it later. When you think about something, why not write it down to share it?
This medium post is just an overview of the series of 3 articles I’ve written on my blog:
Consider reading it to get the full scope.
At first, it was supposed to be something quite straightforward: I just wanted to demonstrate the power of the typeclasses, and the “genericity” (not to be confused with generics!) they offer: they are not tied to any specific implementation.
def prog[A, F[_]: Console: Async: DbAccess: Drawable](p: F[A]) = ???
- Typeclasses just declare a “contract”, a “protocol”, a “capability” that some types (here F[_]) have to fulfill.
- They provide ad-hoc polymorphism: a “has-a” relation instead of “is-a” that we find in OOP through inheritance.
- They have their own constraints: they must be coherent. What if a type has 2 distinct implementations provided? Who wins?
- They can replace a stack of Monad Transformers and lead to better performance, less code, easier maintenance.
- The implementations can be very different: they can boost the application performance just by changing one line! Or they can crash it... (if not stack-safe for instance)
It leads me to learn about Connascence of Types (other types of connascence are described). The connascence describes the relationship, the coupling, between callers and callees.
The connascence of name
The connascence of name deals with the function and arguments names.
To call a function, the caller must know the name of the function (!) and sometimes the argument names. If you change the name of the callee: all its callers must change too.
Same for the argument names, but it’s often not necessary: not all languages handle argument names when calling a function “f(id=11)”, they just handle the argument positions “f(11)”. But how to distinguish between if we didn’t swap arguments if our function is “f(bool, bool)” ?
The connascence of type
The connascence of type deals with the types of the arguments and the return type of a function.
There are other types of connascence: position (of arguments), algorithm (implementation details), etc.
After the connascence, I had to talk about the Free theorems.
They are the idea of Philip Walder: it‘s everything you can say about a function, just by looking at its signature.
def something[A](a: A): A
Guess: what does this function can do? (hint: not only what you think)
A nice and well-known talk from him is Propositions as Types, where he describes the power of induction thanks to types.
It’s strongly linked to polymorphism and the Functional Programming principles. Functions must be:
- Total: no partial functions
- Deterministic: given a fixed input, the output should be the same
- Side-effects free: no mutation outside the function scope (like println)
Without those rules, types can lie. Imagine you return null or throw an exception: does it appear in the return type? No! (don’t talk about throws in Java). In Scala, we never use null: we use Option[A] to declare it’s nullable.
Types: Never commit too earlyI hope this small overview will tickle you. My series talks about that and much more. You could probably learn something from it, don’t hesitate to check it out.'