DEV Community

Roman Koshchei
Roman Koshchei

Posted on

Unions are bad! What to use instead.

I decided not to use union types as you see them usually. Because they aren't well supported in many languages. And I have a better replacement.

One of

This means input will be only one of the specified types. You will ask: "Are union types the same?". Almost, but the implementation is different. Let's look at some examples.

Typescript union types

type Aircraft = {
    weight: number,
    capitan: string
}

type Car = {
    weight: number,
    driver: string,
    engine: string
} 

type Transport = Aircraft | Car
Enter fullscreen mode Exit fullscreen mode

That's union type. To recognize which type of object is, we can check is the engine is not undefined. But the usual practice is to create a separate field for defining type:

type Aircraft = {
    type: 'aircraft',
    weight: number,
    capitan: string
}

type Car = {
    type: 'car'
    weight: number,
    driver: string,
    engine: string
} 

type Transport = Aircraft | Car
Enter fullscreen mode Exit fullscreen mode

And check if the type equals 'aircraft' or 'car'.

What's the problem?

If my lang doesn't support union types I got some limitations. First of all, I should get a dynamic object, then check if it contains a type field, and only then cast them as some type. Not actually cool.

What to do?

We will use OneOf type. This means the object can contain only 1 of the specified types. How to implement:

public class Aircraft
{
    public double weight;
    public string capitan;
}

public class Car
{
    public double weight;
    public string driver;
    public string engine;
}

public class Transport
{
    private Aircraft? aircraft;
    private Car? car;

    public Aircraft? Aircraft
    {
        get => aircraft;
        set { aircraft = value; car = null; }
    }

    public Car? Car
    {
        get => car;
        set { car = value; aircraft = null; }
    }

    public Field(Aircraft aircraft) => this.aircraft = aircraft;

    public Field(Car car) => this.car = car;

    public static implicit operator Transport(Aircraft aircraft ) => new(aircraft);

    public static implicit operator Transport(Car car) => new(car);
}
Enter fullscreen mode Exit fullscreen mode

What did we get? In transport, type can exist only 1 field because on set all other fields will be set null. And we can return Car or Aircraft types because we have implicit casts for them. It's also typesafe because you HAVE TO check if fields are not null. The only bad thing is more code, but Vesna will generate them for you (when will be v1).
It will solve problems of universality. Also checking if is not null requires fewer resources than comparing strings (like __typename in GraphQl). If you use the same lang on the front and back (maybe tRPC) and will not use something else, then you can use union types. But my opinion is there, I will be glad to discuss more if you want, just write a comment!

Top comments (0)