My first impression after the podcast was that we were not at the level to defend dynamic languages properly and I ended very surprised about the modern features of static typed languages.
Lots of things were told during the conversation and, in my opinion, lots of reasons arose to make static typed languages shine over dynamic ones. After 700 km of highway, I’ve got to sort my ideas and conclusions about the podcast. This is a long post, so be prepared!
Types are meaning
From the bare metal perspective, types are nothing. The pure hardware executing the programs in our devices understand only about memory addresses and data sizes. It does not perform any type-checking before running the assembly code, once the code it’s loaded, it’s on its own.
You start introducing types to mean something. Consider simple types in C: they are all about data sizes (int, word, byte), formats (float, double, pointer) and access (const) but you’re helping the compiler to create better target code: memory efficient, faster and safer. In addition, C gives us ways to combine simpler types into complex ones as well by using DEFINE macros and structured types. C is able to calculate each size, format and access type of the new abstraction preventing us from accessing invalid fields of a record or using them in incorrect places but, in addition, C makes new names to be charged with unique meaning (i.e two structures differing only in the name of the structure are actually different types) so we can abuse this feature to create abstract relationships.
This is what I think when talking about types. Types are (or at least, add) meaning. You use types as a way to perform a classification of data, to label some properties that some set of values should have and to establish relationships with other types.
In favor of Static Typing
Considering what I said it’s not surprising that one of my favorite arguments supporting static typing is the types as algebraic structures reasoning mentioned by Alvaro Polo. Simply put, algebraic structures are sets of values and operations. In the programmer mindset, structures are abstractions, values are instances of these abstractions and operations are relationships.
Don’t be naive, defining abstractions and APIs is something any experienced programmer does. It does not matter they use dynamic or static languages. Properly naming things improves code readability and maintainability, something referred by Sebastian Ortega when talking about types as documentation.
But we know for sure this naming stuff is only convenient for us, the programmers. For the language, developer defined names mean nothing. Actually, names mean something to us because the implicit knowledge we have about the relationships between these names. For instance, when talking about persons, companies and employees we know that if a company hires a person, they become an employee. We know this due to our personal experience.
These knowledge networks and implication chains resemble what Sebastian said about types as logical predicates. Precisely, APIs exist to explicitly formalize these relationships in terms of programming languages. Once we have the mechanisms to express relationships between entities we can automatically check if these relationships hold during the execution of the program, i.e. at runtime.
The interesting thing is to notice that once we know how new data is created and where in the source code, we can check these relationships even before the program starts to run which is precisely what static typed programming languages do avoiding the program to crash if they try to perform illegal operations on certain data.
No runtime crashes mean no corrupted files, no inconsistent data bases, no zombie processes, no memory leaks… This is the first true argument supporting static typing and not talking merely about the importance of types in general: reasoning on types as a way for early error catching as the program is guaranteed to not crash at runtime due to illegal operations.
I think most of the tied discussions between dynamic and static typings are consequence of they being actually discussing about just types and both perspectives agree on types are actually a really cool thing: they help us to distinguish entities and establish relationships and both static and dynamic languages offer mechanisms for the user to define new types. What static typed languages actually provide is a type-checker running before executing the program. So type errors are harmless and runtime consequences are void.
In favor of Dynamic Typing
I mentioned one benefit of static typed languages. And this is an important one then, why do dynamic languages continue to success gaining more and more traction and followers? For me, this is answered during the podcast by Luis Osa when talking about cognitive load and I tried to bring the topic to the front when talking about the ownership-checker of Rust.
What dynamic typing languages offer over static ones is precisely the lack of precheck stages. You write your program and your program begins to execute. You start with a fuzzy concept of what you want to do and a generic type object (realize dynamic languages only have one generic type) in your program then, progressively, you differentiate some objects from other ones by specifying classes, APIs, traits… The program evolves in complexity as your mindset does. And this is what I think Luis was referring with cognitive load.
So, the true discussion arise when realizing we are not talking bout types or benefits of types but about reasoning on types and when, how and by whom this is performed.
But let me ask you something? Is the precheck (or the lack of precheck) stage something intrinsic to the nature of typing or a language feature? My personal answer is that both are language features. We were talking about the lack of types and checking stages of static typing but what about simply allowing these languages to disable these checkers in the same way you can disable some warnings while programming? Or what about introducing a dynamic type to static typed languages to deal with generic structures? Alvaro and Sebastian talked about rep types in Haskell allowing you to define new types on the flight.
In the other side, what about linters? What if the source code could be the input of a variety of algorithms, each in charge to perform some kind of checking. One checker could perform type checking a la C, where all types should be explicitely annotated and other could use type inference a la Haskell. Check mypy for instance, a static type checker for Python! Or look at Lisp macro expansion, which are pieces of executable code that receive the program AST itself before executing it.
Sebastian said in the podcast that typing could affect the compiling process. Do you want a rewrite stage in a dynamic language? What about Babel or Closure Compiler? Of course they are not performed by the runtime environment. Neither it does in a static typed language.
Do you think these are language features? I agree, they are. What about introducing macro expansion in Python? Think about special notation allowing a function to receive the module AST to be transformed before continuing execution, all at runtime so you are free to choose if during this macroexpansion time you have side effects or not or if it is performed at the beginning of the execution or in the middle of it.
I cheated. I was using titles for sections introducing arguments in favor of one or other approaches but what I really did is to argument why types are cool and talking you about language features. Now seriously, if you consider the historical approach of static and dynamic typed languages, you’ll see they derive from Alonzo Church and Haskell Curry perspectives about typing. Church perspective is called intrinsic while Curry’s is called extrinsic.
This is because Church introduced a kind of calculus which turned out to be inconsistent so he fixed it by introducing some restrictions to avoid paradox. Simply put, if you consider these restrictions as a whole with the calculus you will be aligned with Church’s intrinsic types while if you consider it an addition you will match Curry’s extrinsic approach.
I’m more aligned with Curry’s extrinsic perspective as I think all the meaning should be (ideally) in the program itself to allow me to opt in if I want.
The proof of the success of dynamic languages is out there, see the Web!. The accurate causes? I don’t know for sure but my guess is it’s related with optional complexity. Do I want a code-style checker? Let me add it. Do I want a type checker? Let me choose which one. Do I want a ownership checker? Let me add it but allow me to remove it later! Do I want to jump without parachute and go to production without checkers? The decision is all mine.
In addition, there are some smells: think about linters and you understand you’re adding another program taking a source code and analyzing it. You are not touching nor expanding the original language but adding external reasoning. Nevertheless, you see some additions to static typed languages to make them more dynamic. Curious.
I don’t want to deceive here because, ultimately, we were not talking about dynamic languages and static ones (there is a subtle difference we can discuss in another post) but about dynamic or static typing schemes.
At the end of the day I feel more comfortable with dynamic typed languages. I’m a strong believer of types and all their good parts, I’m a strong defender of composition over inheritance and see new types as intersection of API, types, traits, whatever simpler building blocks. I like building complexity on simplicity and I think dynamic typing or unittyping is more suitable for composition. Furthermore, language features exhibit from traditionally dynamically typed languages allow me to develop faster and opt into new reliability features as I need them.
Hope it helps!