DEV Community

Cover image for How to use Type Hints in Dynamic Languages : Python, PHP, JS
Amine
Amine

Posted on • Edited on

1

How to use Type Hints in Dynamic Languages : Python, PHP, JS

You can also 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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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'>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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.

Image: If I wouldn't do that if I were you

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Image: Python and PHP are -and will remain, dynamic typing languages according to their respective docs

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.

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (5)

Collapse
 
xwero profile image
david duymelinck

I was always bothered by static typed languages because you are forced to use types for everything. For example in c# and java you have to overload methods when you call them with different parameter types or more or less parameters.

Maybe you could add an extra benefit of static typing, documentation. I don't see typing not as a complication, more as a way to make the code easier to understand.
for example fn ($a, $b) => $a + $b; If you type hint the parameters with int you understand it is used to sum the parameters. If you type hint them with array you understand it merges the arrays.

I love the way how it is added to dynamic languages. You are in control of the level of type hinting that is required for your project. And you can ramp up or down the level for different parts.

If you look at rust and go, and other modern/newer languages, they are using type inference which is as I understand it making the typing looser for a heavily typed language. Dynamic languages come from another direction, but have the same functionality than the modern languages. That is a win in my book.

Collapse
 
aminehorseman profile image
Amine

I cannot agree more with what you said: "I love the way how it is added to dynamic languages. You are in control of the level of type hinting that is required for your project."
As example I was once doing buch of mathematical calculations where I know my variables are all float numbers and will stay floats as it implies mostly multiplications and no calls to external libraries, why bother myself with typing it these kind of situations for example.

Collapse
 
xwero profile image
david duymelinck • Edited

The only thing I have a problem with in your comment is "I know" and " why bother myself"

I assume all my code will be read by others. That is why I think typing is a part of the code documentation.
It is the same as using Interfaces. Interfaces on their own do nothing, but they are the design of the class. Which makes them very powerful.

Thread Thread
 
aminehorseman profile image
Amine

Putting it that way, it makes sense, yes.
The situation I'm sharing should not be generalized; the code was shared only with some researchers that knew exactly what these mathematical equations expect as entry and what result would be returned.

Collapse
 
aiamk profile image
Abdulla

great article, i totally agree with you

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay