Variant vs record type

Hi,

So based on the answer from @chenyan from here: DRY class inheritance in Motoko - #3 by mymikemiller

If we remove `#` , the Node type becomes a record type, which means the Node type contains both `element` and `text` , which is not what we want.

I’m trying to understand the difference between

type T1<T> = { #test : T; };
and
type T2<T> = { test : T; };

I do understand the non variant one as it behaves like a simple struct but can’t wrap my head around when it’s best to use one or the other and the documentation is a bit poor so doesn’t really help me.

Also

let t2: T2<Nat> = { test = 1; };
let t1: T1<Nat> = { #test 1; };

t1 gives the following error: warning, block syntax is deprecated in this position, use 'do { ... }'

Could someone please explain to me? Thanks in advance.

The way I understand it is that when a type has multiple variants (multiple member variables prefixed with #), it allows you to choose (and test for) which of those variants it is. You can only choose one of them at a given time. Your example only has one variant, so it might work but it’s not very useful.

A better example might be with shapes:

public type Shape = {
    #square: Nat;
    #circle: Nat;
    #rectangle: { width: Nat; height: Nat };
};

If I choose to create an instance of the square variant of Shape, I cannot specify a width and height, I can only specify the one Nat that the square variant has.

Some examples of instantiation would look like this:

var mySquare = #square 2
var myCircle = #circle 3
var myRectangle = #rectangle { width=4; height=5; }

*note that the error you were getting on t1 is because you unnecessarily used curly braces and the compiler thought you were trying to create a block.

Variants allow you to achieve a type of polymorphism when used in this way. All these objects are Shapes and can be passed around as such, but they each have their own data specific to them, as though they were more specific subclasses of Shape.

Variants also let you do some pretty cool stuff with pattern matching, for example you can switch on which variant it is. See the tree example, which does something different depending on whether a node is a leaf or a branch. This is kind of like an enum, except each possible value for the enum can also contain extra data, like the radius or width/height in my Shape example. If you just want a simple enum, you can specify the variants without specifying that the variants can contain any data:

public type Shape = {
    #square;
    #circle;
};

If you want every instance of Shape to contain certain data, leave the # off and they’ll all get the member variable:

public type Shape = {
    #square;
    #circle;
    size: Nat;
};

If I’ve said anything wrong or incomplete, I hope someone at dfinity will correct me. I’m new to all this too.

8 Likes

Nice explanation! I’m not sure that last example will work though, the variant should only contain tags at the root. ( Try pushing it through the compiler and see what it has to say about it ; )

@Ori You’re right, it complains about size being an unexpected token.

Is it possible to create a type that can be one of a couple variants, but always has a given field? Like my example, where a Shape can either be a square or a circle, but in both cases has a size attribute? Is it possible to create this type without having to redundantly specify the size attribute on both variants?

You can nest the variant inside a record.

type ShapeKind = {
    #square;
    #circle;
};
type Shape = { shape: ShapeKind; size: Nat };
1 Like

Just a minor addition:

type Shape = {
    #square;
    #circle;
    size: Nat;
};

is not valid. Either you define a variant (then all names need to be #-prefixed), or a record (then all names have no #).

So you probably want to do what @chenyan says.

You do not have to name the inner type, of course; the following is equivalent:

type Shape = { shape : { #square; #circle}; size : Nat };
3 Likes

Heya,
Great answers from @mymikemiller and @nomeata.
I started reading docs just today, and I was confused only by variants. I don’t have any prior knowledge of Rust, Motoko, etc.

I want to ask one more question from example above:

var myRectangle = #rectangle { width=4; height=5; }

Am I able to extract width or height data from myRectangle?
myRectangle.width wont work

I was only able to retrieve values by writing switch:

type Shape = {
    #square: Nat;
    #circle: Nat;
    #rectangle: { width: Nat; height: Nat };
};

var shape: Shape = #circle 10;
var shape2: Shape = #rectangle{width = 20; height = 30; };

switch(shape2) {
  case (#square(x)) { x + 10 };
  case (#circle(x)) { x + 20 };
  case (#rectangle{width; height}) { width + 100 };
}

Or the only way is to define it as @chenyan suggested?

type ShapeKind = {
    #square;
    #circle;
};

type Shape = { shape: ShapeKind; size: Nat };

Looking forward to your answer.

Have a nice day,
Cheers (:

Switch is the safe way to go to access the payload of a variant, since it forces you to consider all the cases.

If you really know the variant in hand, then you can safely use a let, something like:

let (#rectangle{width;height}) = shape2;

but the compiler will warn you about that being dangerous.

(Tagged union - Wikipedia has an ok description of variants in various languages, if that helps)

1 Like

Great thank you. It’s clear now.