Thoughts on dynamic typing (part 1)

As a professional programmer, I've been raised on a JVM diet of Java and, more recently, Kotlin. So, statically typed languages. Such languages have great characteristics, code navigation with an IDE is implemented well, refactor tooling is also powerful. Many errors are caught at compile time and the typing provides a kind of documentation for the code. You still need actual documentation but the types definitely help with comprehension and code discovery.
And yet, recently I've been feeling an itch to dig into dynamic programming languages. I find it interesting that the internet is filled with criticism of dynamically typed languages and yet, some very powerful and widely used tools are built – to a big extent – with the help of dynamically typed languages. Here are some examples:
- Shopify uses Ruby
- Dropbox uses Python
- Spotify uses Python
- Protonmail uses PHP, they have open-sourced part of their code
- Homebrew (a MacOS package manager) appears to be using Ruby extensively
- Javascript (nowadays often enveloped by Typescript) is used by an infinite amount of websites
So, if dynamic typing is bad, then why are successful projects still using them? Most of these companies no longer have money constraints, they could just go and rewrite in a different language. X (Twitter) and Airbnb appear to have done this for example so why doesn't everybody do it?
I think that while dynamically typed languages definitely have limitations, they also have advantages. Their advantages are easier to leverage in smaller, tightly-knit teams of programmers with sufficient experience. Programmer experience is key here because the language is more permissive, this added freedom can backfire if not handled with care.
Duck typing is central
There is an "imperative way", an "OOP way" and a "functional way" of doing things. If you try programming in a specific paradigm, but fail to grasp the rules of the game of that paradigm, you are going to have a hard time. I expanded on this in my previous blog post: Untangling programming paradigms. There is also a "dynamically typed way" of doing things and I think duck typing is central to it.
Relying on types and methods that are provided by the language too much can make you lose track of what objects you are handling. In a statically typed language, the types are permanent hints as to the data you are handling. Map<UUID, Map<UUID, Order> userOrderMap
is more readily interpreted as a "map of orders grouped by user id". Obviously, you can still have the same data-structure in a dynamically typed language but over time the nature of the contents of this data-structure can become unclear or forgotten, refactors could make well-intended comments obsolete.
So what's the solution? I think the solution is to introduce some ducks! The fixation on types is a very statically-typed-language-thing to do. Dynamically typed languages are more focused on the behavior of things. Most of the time, it doesn't matter what type something is, so long as it does what I want it to do. But what do I want it to do? I want it to quack and to walk like a duck! This means, that I need to introduce custom objects to express the intended behavior. the []
operator of the map data-structure is too abstract, it does not convey the nature of the thing I am interacting with. Sure, I can guess that it's some kind of collection, but that's about it. Custom methods can communicate a lot more thanks to their names.
A different example. We have some kind of student service that keeps track of students:
require_relative "student"
module Student
class StudentService
def initialize()
@students = Hash.new
end
def add_student(student)
raise 'student with this name already exists!' if @students.key? student.name
@students[student.name] = student
end
def get_student_by_name(student_name)
@students.fetch(student_name) {|name| raise "no student with name '#{name}' was found!"}
end
end
end
The 2 methods of that class give pretty good hints as to what parameters are expected, it's easy to figure out how to interface with this class, even without explicit types.
I think get_student_by_name
is becoming somewhat verbose but this is the lesser evil. It's still easy to pronounce and comprehend, typing it is also not too painful. It's important to remember than even though the language itself might have some nifty syntax to express things in a compact way, custom code should err on the slightly more verbose and explicit side. Language level operators are introduced once and accepted by all the programmers. Your custom codebase does not benefit from the same level of alignment, it's better to make things easier for your team members (both old and freshly-hired) and just spell things out.
Takeaway
In a dynamically typed language, if you don't create any "ducks" then you don't benefit from duck typing. Relying too much on types provided by the language can lead to code that's hard to comprehend.
Another helpful practice is to use named parameters – if the language allows – and to keep method parameters as few as possible. Actually, these ideas are beneficial in any language.