Take advantage of type aliases in Dart
Tags: dart, flutter, aliases
Published: 2021-05-24 20:16:35

Dart 2.13 introduced type aliases for **all types**, not only function types. Like in the example provided in [the official announcement](https://medium.com/dartlang/announcing-dart-2-13-c6d547b57067), `typedef Json = Map<String, dynamic>;`,  type aliases provide syntactic sugar that helps the developing process.
Let's see how we can exploit this new feature of Dart, and how not to use it.

## Mask generics

As for the `Json` example, we can **specify a generic type** the is used in the same way in our code. If **everywhere** in our code, when we handle json data, we expect it to be of type `Map<String, dynamic>`, then we can just type-define it as `Json` and just use it as a new type everywhere.

In a more complicated case, let's say we have an app where we sell our **products** and **services**. We created a `PaymentService` that triggers a **payment** event, based on the cart and a given payment method, like credit card, or bank transfer:
```dart
abstract class PaymentMethod {}
class CreditCard implements PaymentMethod {}
class BankTransfer implements PaymentMethod {}

class PaymentService<ItemType, Method extends PaymentMethod> {
  ...
}
```
We did this so that we can **isolate** credit card payment **specific code** in its specific class, and the same for bank transfer specific code. When we want to use `PaymentService` in our code, we specialize the generic type. For instance, a ShippingService might need to access payment services specialized on both credit card and bank transfer:
```dart
class ShippingService {
  final PaymentService<Product, CreditCard> creditCardPayment;
  final PaymentService<Product, BankTransfer> bankTransferPayment;
  ...
}
```
Those specialized generic types are always the same inside `ShippingService`, and they make the code **harder to read**. In this case we can define some easier-to-read types, one for `CreditCardPayment` and one for `BankTransferPayment`.
```dart
typedef CreditCardPayment = PaymentService<Product, CreditCard>;
typedef BankTransferPayment = PaymentService<Product, BankTransfer>;
```
We can now refactor `ShippingService` to a **cleaner** version:
```dart
class ShippingService {
  final CreditCardPayment creditCardPayment;
  final BankTransferPayment bankTransferPayment;
  ...
}
```
The detail implementation is now **hidden** in the new type name, but it can still be **accessed** by reviewing the typedef statements. We do not need to use the long specialized types anymore.

## Better semantic
Another situation where type aliases can help us write **better code** is by giving the proper **semantic** to variables and class properties.

Let's say we define the dimensions of a product as
```dart
class Product {
  /// Height in centimetres.
  final double height;

  /// Length in centimetres.
  final double length;
  
  /// Width in centimetres.
  final double width;
  
  ...
}
```
There is nothing wrong here, but we can make it easier to read by writing some **self-documenting code** by using type aliases. We could define a `Centimetres` type alias:
```dart
typedef Centimetres = double;

class Product {
  final Centimetres height;
  final Centimetres length;
  final Centimetres width;
  
  ...
}
```
Now not only the type specifies the unit of measure, making the comments **unnecessary**, but if we find out that those dimensions should be expressed with an integer instead, we **only need to change the alias**.

## Generic function type

The possibility to create an alias for a generic function is available in Dart since version 1.24.

A generic function type allows us to treat functions as [first-class citizens](https://en.wikipedia.org/wiki/First-class_citizen), even from a **readability point of view**, using them **everywhere a type is expected**, for instance in type annotations, return types, and actual type arguments. It also helps by improving **readability**, which, as for long generic types, is a problem for complex function signatures.

Continuing the payment example above, let's say that we have a function that calculates the tax owed, given the products. We can **define the type** of such function as:

```dart
typedef CalculateTaxes<Product> = int Function(List<Product>);

void processPayment(List<Product> products, CalculateTaxes<Product> taxHandler) {
  ...
}
```
This change does not seem to improve a lot, but with **more complicated** function definitions, it could make a bigger change for code readability.

## When not to use type aliases

Not all cases where we could use type aliases are good cases. If a type alias does not improve readability, and **does not provide value**, it should **not be used**.

For instance, if we use a list of products in our app, while the user is adding and removing products from this list, we can see it as the **cart** for that user.
```dart
typedef Cart = List<Product>;
```
But this `Cart`, as defined above, **does not improve readability and does not add value**. I would expect to be able to get the total of a cart by calling `cart.total`, but `total` is not available for `List`.

If we want to have a `Cart` type, we should consider **creating a new class** that contains a list of products, instead.

## Conclusions

Type aliases are a very simple language feature that can be exploited to **improve the readability** of our codebase, but we should **not over-use them**, and we should consider carefully where a type alias is a good idea or just an over-complication.