The Irony of Static Typing in Dynamic Languages
How to balance dynamic and static typing in Python and PHP
Read this article on Medium.
It's always funny when we see how programming languages evolve over time.
One upon a time, when I started my journey in the software development world, dynamic languages such as Python, PHP and JavaScript were appreciated for their flexibility and concise syntax suited for rapid development.
However, as these languages evolve, they incorporate new features inspired by statically?typed languages like C++ and Java. One such feature is explicit typing, which encourages programmers to declare the data type of variables when they are first defined.
Why this shift?
Dynamically typed languages such as Python, PHP, and JavaScript are designed to let the interpreter imply automatically the type of variables during the runtime:
# In python and PHP: 'y' will take the same type as 'x'
x = 3.14
y = x // y = 3.14 (float)
However, when we explicitly define the types of variables in static languages, we allow the compiler to catch the errors during the development phase before executing the program, while providing him a hint about the memory size to allocate to these variables, and the possibility of making some optimizations in the generated machine code.
// C++ example: 'y' will be an integer
float x = 3.14;
int y = x; // y = 3 (ignored the decimal part of the number)
How explicit-typing is introduced in dynamic languages
In the following example, we declare the same function using explicit and implicit typing.
Python:
# using the classic syntax:
def add(x, y):
return x + y
# using explicit typing:
def add(x: int, y:int) -> int:
return x + y
JavaScript / TypeScript:
// using the classic syntax
function add(x, y) {
return x + y;
}
// using explicit typing
function add(x: number, y: number): number {
return x + y;
}
PHP:
// using the classic syntax:
function add($x, $y) {
return $x + $y;
}
// using explicit typing:
function add(int $x, int $y): int {
return $x + $y;
}
Where is the irony ?
Don’t take this article as an objection to these new features, I do acknowledge the advantages of using strictly typed languages. However, using type annotations in Python, for example, doesn’t stop you from changing the types of your variables:
?x: int = 0
x = "John"
print(type(x)) # <class 'str'>
One might ask why the interpreter allows us to execute this code then?
That’s because these languages are built that way: they are “dynamically typed” by definition. Which means they don’t enforce variables to keep the same type, at least not by default.
In PHP, you can ask your interpreter to be more rigid by setting ‘strict_types’ to true. This way, it won’t allow changing the data types during runtime:
?declare(strict_types=1);
While in python, you can use the ‘mypy’ package to analyze your code and catch the bugs during the developemnt phase:
?$ mypy program.py
error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
You can see 'mypy' as an advisor telling you what you did wrong, but it doesn't stop you from executing your code at your risk.
But what if I don’t want to restrict my variables to one type only?
A new feature lets you reduce the list of accepted types for a variable by using the union operator. The following example shows how to do it in the 3 cited languages:
y: int | float = f(x) # introduced in Python 3.10
int | float $y = f($x) // introduced in PHP 8.0
let y: number | string // typescript
Are we sacrificing code readability?
Let’s look at an example function that prints the items of a dictionary. Here’s the initial version:
def print_attributes(**kwargs):
for key, value in kwargs.items():
print(key, value)
person = {"name": "John", "height": 1.84}
print_attributes(**person)
By using the recommendations from PEP 692 introduced in Python 3.12, the code becomes:
from typing import TypedDict, Unpack
class Person(TypedDict): # create a class inheriting from TypedDict
name: str
height: float
def print_attributes(**kwargs: Unpack[Person]) -> None: # use the Unpack operator
for key, value in kwargs.items():
print(key, value)
person: Person = {"name": "John", "height": 1.84} # create an instance of the class
print_attributes(**person)
As a result, our code doubled in size, and we had to create a class that inherits from TypedDict, while using the Unpack operator to tell mypy that the received object is a TypedDict.
Even if the new code is safer, it would take more time for python beginners to understand it compared to the previous version.
But it’s not just about readability; it’s also about productivity. Ten years ago, I chose Python for my PhD project because of its simplicity and the ability to prototype new ideas quickly. Over time, my codebase grew larger, and updating it now with type annotations would take a long time.
Fortunately, we have the freedom to apply type hinting selectively. We can use it for some parts of our code while leaving other parts unchanged.
When should we use it?
Don’t feel pressured to rewrite your entire codebase just because you learned a new, shiny feature.
These new features are like tools. My advice is to use them wisely:
Use static typing in the following scenarios:
Use dynamic typing if you are:
These are general recommendations, don’t try to strictly follow them, only you know if they apply to your specific use cases.
Keep in mind that when it comes to coding, the golden rule is always to avoid complicating things when it’s not necessary.
?
freelancer
4 个月aitutorialmaker.com AI fixes this Indeed.
Graduate Teaching Assistant at Université des Sciences et de la Technologie Mohamed Boudiaf ORAN
4 个月my man back at it again!