List/dictionary of a type within a type? Or alternative?

Hi All,

Hope you’re all well. I’ve found myself in a slight conundrum. I have a basic type, and I would like another type to incorporate (potentially) many of this basic type. For example, I have this type:

public type Person = {
    title: Text;
    name: Text;
};

I would like this type to be incorporated into this other example type:

public type Guild = {
    guildname: Text;
    guilddesc: Text;
    guildmembers: [Person]
};

This example is how I’ve tried doing it, but I’d end up with errors such as:

type error, operator not defined for operand types and type error, expression of type [1] cannot produce expected type [2]

I suppose what I’m asking here is; is there a better way of including a list of a type within a type? Or is it better off being Text and doing something else? Has anyone else encountered this? Would love to hear from you!

Kev

2 Likes

Hi Kev,
this is actually a greate question and probably a pitfall for people not familiar with Motoko, I’m super glad you asked this and it took me a little while to figure out for myself!

Your Person object type has the implicitly immutable field-types titlel : Text and name: Text. Motoko seems to expect those to already exist when declaring an immutable object of the corresponding type (notice the let keyword).

This will not work:

public type Person = {
    title: Text;
    name: Text;
};

let katie : Person = {
    let title : Text = "CEO";
    let name : Text = "Katie";
};

But this will (notice the var keyword):

public type Person = {
    title: Text;
    name: Text;
};

var katie : Person = {
    title  = "CEO";
    name = "Katie";
};

The code below shows a compiling example of how to use a object type with immutable and mutable type-fields:


actor test {    

    type ImmutablePerson = {
        title: Text;
        name: Text;
    };

    var lisa: ImmutablePerson = {
        title = "Expert";
        name = "lisa";
    };

    // this won't work as the field-types of
    // ImmutablePerson Guild are immutable!
    // lisa.title := "Noob"; // error: expected mutable assignment target

    let titel_bernd : Text = "CEO";
    let name_bernd : Text = "Bernd";    

    let bernd : ImmutablePerson = {
        title = titel_bernd;
        name = name_bernd;
    };

    let titel_katie: Text = "CEO";
    let name_katie: Text = "Katie";    

    let katie : ImmutablePerson = {
        title = titel_katie;
        name = name_katie;
    };

    type ImmutableGuild = {
        guildname: Text;
        guilddesc: Text;
        guildmembers: [ImmutablePerson];
    };

    let guildname_berserkers: Text = "berskerser";
    let guilddesc_berserkers: Text = "very good";
    let guildmembers_berserkers: [ImmutablePerson] = [bernd, katie, lisa];

    let myguild : ImmutableGuild = {
        guildname = guildname_berserkers;
        guilddesc = guilddesc_berserkers;
        guildmembers = guildmembers_berserkers;
    };

    var mysecondguild: ImmutableGuild = {
        guildname = "sharks";
        guilddesc = "strong guild";
        guildmembers = [lisa, bernd];
    };

    // this won't work as the field-types of
    // ImmutableGuild are immutable!
    // mysecondguild.guilddesc := "weak guild"; // error: expected mutable assignment target


    type Person = {
        var title: Text;
        var name : Text;
    };

    type Guild = {
        var guildname: Text;
        var guilddesc: Text;
        var guildmembers: [Person];
    };

    var kai : Person = {
        var title : Text = "User";
        var name : Text = "kai";
    };

    // we can easily change kais title
    // as the field-types are mutable
    kai.title := "CEO";

    let joe : Person = {
        var title : Text = "admin";
        var name : Text = "joe";
    };

    let mary : Person = {
        var title : Text = "admin";
        var name: Text = "mary";
    };
    
    let guild : Guild = {
        var guildname : Text = "MyGuild";
        var guilddesc : Text = "Great Guild";
        var guildmembers: [Person] = [mary, joe];
    };

    // of course it is also possible
    // to change the list of guildmembers
    // as this field-type is again mutable
    guild.guildmembers := [kai, mary];
}
1 Like

It is a bit weird though, as I think I remember this has worked before. Looking at this old post Enzo’s and my examples seem to do the same thing:

[EDIT]: I updated the above example, what I oversaw was the var keyword when initalizing an object!

3 Likes

Hi Moritz, thanks for your well explained response! I’ve got a question about the second part of the block of code:

Does adding var circumvent the immutability of the type? For example, if I added var before each characteristic, does it become mutable? I’m just trying to understand the difference between the first two types you wrote and the second two.

I suppose what I’m asking, is which way is more sensible?

ImmutablePerson = {
    title: Text;
    name: Text;
};

var lisa: ImmutablePerson = {
    title = "Expert";
    name = "lisa";
};

let titel_bernd : Text = "CEO";
let name_bernd : Text = "Bernd";    

let bernd : ImmutablePerson = {
    title = titel_bernd;
    name = name_bernd;
};

let titel_katie: Text = "CEO";
let name_katie: Text = "Katie";    

let katie : ImmutablePerson = {
    title = titel_katie;
    name = name_katie;
};

type ImmutableGuild = {
    guildname: Text;
    guilddesc: Text;
    guildmembers: [ImmutablePerson];
};

let guildname_berserkers: Text = "berskerser";
let guilddesc_berserkers: Text = "very good";
let guildmembers_berserkers: [ImmutablePerson] = [bernd, katie];

let myguild : ImmutableGuild = {
    guildname = guildname_berserkers;
    guilddesc = guilddesc_berserkers;
    guildmembers = guildmembers_berserkers;
};

or:

var lisa: ImmutablePerson = {
        title = "Expert";
        name = "lisa";
    };

    let titel_bernd : Text = "CEO";
    let name_bernd : Text = "Bernd";    

    let bernd : ImmutablePerson = {
        title = titel_bernd;
        name = name_bernd;
    };

    let titel_katie: Text = "CEO";
    let name_katie: Text = "Katie";    

    let katie : ImmutablePerson = {
        title = titel_katie;
        name = name_katie;
    };

    type ImmutableGuild = {
        guildname: Text;
        guilddesc: Text;
        guildmembers: [ImmutablePerson];
    };

    let guildname_berserkers: Text = "berskerser";
    let guilddesc_berserkers: Text = "very good";
    let guildmembers_berserkers: [ImmutablePerson] = [bernd, katie];

    let myguild : ImmutableGuild = {
        guildname = guildname_berserkers;
        guilddesc = guilddesc_berserkers;
        guildmembers = guildmembers_berserkers;
    };


    type Person = {
        var title: Text;
        var name : Text;
    };

    type Guild = {
        var guildname: Text;
        var guilddesc: Text;
        var guildmembers: [Person];
    };

    var kai : Person = {
        var title : Text = "User";
        var name : Text = "kai";
    };
    
    let joe : Person = {
        var title : Text = "admin";
        var name : Text = "joe";
    };

    let mary : Person = {
        var title : Text = "admin";
        var name: Text = "mary";
    };
    
    let guild : Guild = {
        var guildname : Text = "MyGuild";
        var guilddesc : Text = "Great Guild";
        var guildmembers: [Person] = [mary, joe];
    };
};

Hey Kev, i updated my initial response for a better (and correct) answer!

The var keywords in the object-type declaration do not circumvent the immutability of the object. They just make the field-types either immutable (as by default) or mutable when explicitly declared. Note this example:


actor test {    

    type ImmutablePerson = {
        title: Text;
        name: Text;
    };

    var lisa: ImmutablePerson = {
        title = "Expert";
        name = "lisa";
    };


    let titel_bernd : Text = "CEO";
    let name_bernd : Text = "Bernd";    

    let bernd : ImmutablePerson = {
        title = titel_bernd;
        name = name_bernd;
    };
    // because we declared a mutable object, it 
    // is possible to redefine what lisa is
    lisa := bernd;

    // because bernd is immutable, we cannot change
    // this object and it will lead to a compiler error
    bernd := lisa; // error: expected mutable assignment target
}
1 Like

Ahhhhh that makes sense! Thank you for that! I suppose it would be better to declare the variables in the way you have done for Lisa, as opposed to Bernd?

1 Like

Lisa is more intuitiv to me personally, if you are not super dependent on lisa being lisa forever, i’d stick with first one.

Maybe this helps to decide?

2 Likes

Thank you for the link! I’m still trying to get my head around why you’d use var over let and vice versa in a situation like this. I’ve seen in the linked-up example that the types: NewProfile and Profile are both immutable and the only difference is that the Profile type then contains the UserId. But if the field Profile was mutable and contained all of the characteristics as variables, could it achieve the same thing?

1 Like

Probably yes, i think it‘s just a design decision. For linkedup it seems like when the profile is „updated“ they just create a new one and assign it to the ID. Maybe this done for security , so your profile can‘t be altered by others, maybe just good practice ¯_(ツ)_/¯ .
But let‘s ask them @stanley.jones @hansl @enzo @chenyan

1 Like

It’s a functional programming approach, where you don’t alter the existing data structure. Largely a design choice I think, but it does have some bearing on your code, since making your structures mutable on the IC places certain restrictions on what you can do with them, eg sharing/passing them between actors.

If you want more (slightly less readable) examples the functional approach is used extensively in the base library modules.

Interested in their thoughts on this too.

2 Likes

This makes sense. I’m asking all of this because I want to know the best way to handle a group of people. Does a group need a groupID which would be handled by principal? Or can members of the group be deemed admins without it and be able to make changes?

I’m also thinking about how to deal with things that would then contain that group. For example:

public type Guild = {
    guildName: Text;
    guildDesc: Text;
    guildMembers: [Person];
    assignedQuests: [Quest];
};
    
public type Quest = {
    questName: Text;
    questGiver: Text;
    questReward: Text;
    questStatus: Text;
    questAcceptors: [Guild];
    questComplete: Bool;
};

In this example, ideally, everyone should be able to see each member of each Guild assigned to a Quest. But only the individuals in the Quest should be able to make changes to the questStatus. How can this be done? Would the quest need a questID? How can you make it possible that Guilds assigned to a Quest can be seen, and Quests assigned to a Group can be seen (publicly). But these things can only be edited by a Person in a Group thats assigned to a Quest? If that makes any sense at all, my apologies!

2 Likes

That’s a nice design challenge, it’s probably something worth bringing up and exploring in the workshop later actually @enzo @hansl @stanley.jones

1 Like

I’m hoping that I’ll be able to later! Fingers crossed!

2 Likes

Hi All,

I’ve been thinking about my query, and I may have a simpler one: Suppose I have:

type Person = {
    id: Principal;
    title: Text;
    name: Text;
};

public type Quest = {
    questName: Text;
    questGiver: Text;
    questReward: Text;
    questStatus: Text;
    questAcceptors: [Person];
    questComplete: Bool;
};

rather than my previous query of having entire Guilds within the questAcceptors field. Is it necessary to map a Person to a Quest in the form of a HashMap or Digraph? Also, is it better practice to map the questAcceptors field to the Person’s profile or their ID? I ask this because I want a Person to be discoverable by Quest.

Lastly, if a Quest is best to be within a list or Array of all created Quests, how can you still have the same manipulability of a Quest as you would a profile (such as in the linkedup example)? Also, what would be the best way to create a Quest as an individual, and then be able to add others to be able to edit its details. I’ve been playing around with creating them as you would profiles, but that doesn’t seem right, as ultimately, there’d need to be multiple Persons with equal admin rights to the edit the Quest.

Thank you!
Kev