The Irony of Static Typing in Dynamic Languages

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.

  • Python: Type hinting capabilities introduced since version?3.5 in 2015, and enhanced in version?3.12 on 2022.
  • PHP: Declared types introduced in version?7 in 2015.
  • JavaScript: Extended by the release of TypeScript in 2012 defined as "JavaScript with syntax for types".

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:

  • When retrieving data from external sources, such as databases, libraries, and APIs.
  • In critical parts of your code where failure is not allowed.
  • When you are developing a large application, or even a small application that you intend to release to production.
  • When you have an existing codebase that is prone to frequent bugs.

Use dynamic typing if you are:

  • Designing a prototype to quickly test an idea. In that case productivity is more important, and you’ll likely run the script only once.
  • Implementing internal logic where you have confidence that the data types of your variables remain constant. For example, computing a bunch of math equations, or executing if-else statements that have no effect on your data.
  • Displaying data on the screen just to get a visual understanding, such as plotting charts or images.
  • Writing automation scripts with no external inputs, such as batch running command lines on the terminal or printing data on the console.

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.

?

Okba Bekhelifi

Graduate Teaching Assistant at Université des Sciences et de la Technologie Mohamed Boudiaf ORAN

4 个月

my man back at it again!

要查看或添加评论,请登录

社区洞察