Wikifunctions:Representing identity

From Wikifunctions

Summary

We are using identity widely in the Wikifunctions data model, such as Z4K1, Z8K5, and Z40K1, and others. It would make sense to use it in some of our existing types where we currently do not, e.g. Implementation (Z14), Natural language (Z60), and Programming language (Z61). Also planned types, particularly enumerations, such as grammatical features, will require identity.

In addition, one of the suggested solutions also can deal with a long-standing question around optional key values.

This document outlines four proposals:

  1. A type for identity
  2. A generic type for identity
  3. A boolean key on keys
  4. A key with a list of markers on keys

Related bugs

Bugs that are related to this design:

  • T282716: ZObject model: Create a type for identity
  • T320557: Consider adding an identity key to the functional model for ZImplementations
  • T282062: ZObject model: Add a key to Z3 to mark a key as optional
  • T290996: Support Optional Keys in Orchestrator
  • T296755: Check current relevance of SELF_REFERENTIAL_KEYS and remove if unnecessary
  • T304682: function-schemata: Fix requiredKeys for user defined validators
  • T315914: Discuss whether we need to consider the case of new keys being added to existing types with instances
  • T296400: When a key of an object is for a Type, default the UX to a reference rather than an instance
  • T343614: Validation of types should check for identity

What is identity, anyway?

Identity is a mechanism to capture what a given object is. Some types, such as string or numbers, do not need an additional key, since their value is their identity. But for other types it is not as straightforward. One example are the two boolean values, true and false, which are structurally not distinguishable. By persisting the object on Wikifunctions, it gets a ZID, and we use that ZID to distinguish the two values. Furthermore, this allows us to add names and descriptions to each object (which effectively allows users to understand the meaning of the given objects).

By using identity we can also ensure that we are the masters of our own destiny: instead of relying on an external authority such as ISO for languages, we can decide and control ourselves which languages we want to support, which languages to differentiate, etc.

Another advantage of identity is that this is the only way to get the Z2 of a given object, and thus to access the name and description of the object.

Identity keys can sometimes be supportive of simpler, more straightforward source code. For example, T321998 (the parent of T320557) was motivated by a desire for the orchestrator to have a cleaner approach in recording implementation-id in the metadata map.

Required work in all proposals

  • Once we have identity available, this would allow the default component to use this information for whether to select references instead of literals.
  • Changes on default component so the selection is properly restricted.
  • Optional: validators could be somewhat streamlined.

Running example

As a running example, we will use the Boolean (Z40).

This is how the definition of Z40/Boolean value looks currently:

{
  "Type": "Type",
  "Identity": "Boolean",
  "Keys": ["Key",
    {
      "Type": "Key",
      "Key type": "Boolean",
      "Key ID": "Z40K1",
      "Names": [  ]
    }
  ],
  "Validator": 
}
{
  "Z1K1": "Z4",
  "Z4K1": "Z40",
  "Z4K2": ["Z3",
    {
      "Z1K1": "Z3",
      "Z3K1": "Z40",
      "Z3K2": "Z40K1",
      "Z3K3": [  ]
    }
  ],
  "Z4K3": 
}

It is entirely implicit that the key defines the identity. We can try to guess it because the key type of the key is Boolean, which is the type we are defining, but that can be a wrong assumption.

A Boolean value for true currently looks as follows:

{
  "Type": "Boolean",
  "Identity": "True"
}
{
  "Z1K1": "Z40",
  "Z40K1": "Z41"
}

Proposal 1: A type for identity

We introduce a new type, Z91/Identity. Z91 is used on the key to declare that this key is an identity.

Defining Boolean

Differences are highlighted.

{
  "Type": "Type",
  "Identity": "Boolean",
  "Keys": ["Key",
    {
      "Type": "Key",
      "Key type": "Identity",
      "Key ID": "Z40K1",
      "Names": [  ]
    }
  ],
  "Validator": 
}
{
  "Z1K1": "Z4",
  "Z4K1": "Z40",
  "Z4K2": ["Z3",
    {
      "Z1K1": "Z3",
      "Z3K1": "Z91",
      "Z3K2": "Z40K1",
      "Z3K3": [  ]
    }
  ],
  "Z4K3": 
}

There is a new type that would have the same definition as Z9. So the value for true (Z41) would look as follows.

{
  "Type": "Boolean",
  "Identity": {
    "Type": "Identity",
    "Reference": "True"
  }
}
{
  "Z1K1": "Z40",
  "Z40K1": {
    "Z1K1": "Z91",
    "Z91K1": "Z41"
  }
}

Required work

  • Add identity type
  • Adapt types and all values that use identity
  • Probably would need a bespoke component for editing Z91 (simple one, very similar to ZReference, while limiting the choices)
  • Will require adapting the backend for every type that uses identity, since the instances have changed

Advantages

  • No changes in the Type type

Disadvantages

  • We would need to change all existing values using an identity and all types that use an identity. But better now than later.
  • Unless we create a bespoke user interface, we would have no guidance when creating instances of this type.
    • But a Validator would catch errors
  • Validators need to be adapted to the new type. The work needs to be synchronized with switching the individual types over.

Proposal 2: A generic type for identity

We introduce a generic type for identity, Z891. Z891 takes the type the identity key expects and returns something like Z91 from Proposal 1, but type safe.

Defining Boolean

Differences are highlighted.

{
  "Type": "Type",
  "Identity": "Boolean",
  "Keys": ["Key",
    {
      "Type": "Key",
      "Key type": {
        "Type": "Function call",
        "Function": "Identity",
        "Type": "Boolean"
      },
      "Key ID": "Z40K1",
      "Names": [  ]
    }
  ],
  "Validator": 
}
{
  "Z1K1": "Z4",
  "Z4K1": "Z40",
  "Z4K2": ["Z3",
    {
      "Z1K1": "Z3",
      "Z3K1": {
        "Z1K1": "Z7",
        "Z7K1": "Z891",
        "Z891K1": "Z40"
      },
      "Z3K2": "Z40K1",
      "Z3K3": [  ]
    }
  ],
  "Z4K3": 
}

There is a new type that would have the same definition as Z9. So the value for Z41/true would look as follows.

{
  "Type": "Boolean",
  "Identity": {
    "Type": {
      "Type": "Function call",
      "Function": "Identity",
      "Type": "Boolean"
    },
    "Reference": "True"
  }
}
{
  "Z1K1": "Z40",
  "Z40K1": {
    "Z1K1": {
      "Z1K1": "Z7",
      "Z7K1": "Z891",
      "Z891K1": "Z40"
    },
    "K1": "Z41"
  }
}

Required work

  • Considerable amount of work to support generic types better
  • Add identity type
  • Adapt types and all values that use identity
  • Will require adapting the backend for every type that uses identity, since the instances have changed

Advantages

  • No change to the Z4/Type type
  • We would have, out of the box, have a nice interface for creating instances that have identity, getting an object selector zoomed in on the right type.
    • But this requires considerable extension on supporting generic types

Disadvantages

  • Generic types are not well supported, and a large hassle. We would need to improve support for generic types considerably in order to actually have the advantages we describe here.
  • We would need to change all existing values using an identity and all types that use an identity. But better now than later.
  • Validators need to be adapted to the new generic type. The work needs to be synchronized with switching the individual types over.

Proposal 3: A Boolean key

Instead of introducing a new type, we extend Z3/Key by another key that is Boolean, and states whether this key is an identity key.

Defining Boolean

Differences are highlighted.

{
  "Type": "Type",
  "Identity": "Boolean",
  "Keys": ["Key",
    {
      "Type": "Key",
      "Key type": "Identity",
      "Key ID": "Z40K1",
      "Names": [  ],
      "Identity": "True"
    }
  ],
  "Validator": 
}
{
  "Z1K1": "Z4",
  "Z4K1": "Z40",
  "Z4K2": ["Z3",
    {
      "Z1K1": "Z3",
      "Z3K1": "Z91",
      "Z3K2": "Z40K1",
      "Z3K3": [  ],
      "Z3K4": "Z41"
    }
  ],
  "Z4K3": 
}

In this case, the value for Z41/true would not change at all.

{
  "Type": "Boolean",
  "Identity": "True"
}
{
  "Z1K1": "Z40",
  "Z40K1": "Z41"
}

Required work

  • Add key Z3K4 to Type Z3 / Key
  • Adapt types that use identity
  • Minor change to backend or frontend required to include this key in the type objects.

Advantages

  • No change to the instances that use identity
  • The out-of-the box interface for editing identity on a type would be OK

Disadvantages

  • We would need to change type Z4/Type
    • But we just did that recently and it went quite smoothly
  • The identity key would be on every key, which might be a bit cluttery for something that, per definition, happens only once per type at most

Proposal 4: A List of markers

Instead of introducing a new type, we extend Z3/Key by another key that takes a list of Z33/key markers. Z33/Key marker is a new enumeration type that has one value, for now, Z34/Identity.

Defining Boolean

Differences are highlighted.

{
  "Type": "Type",
  "Identity": "Boolean",
  "Keys": ["Key",
    {
      "Type": "Key",
      "Key type": "Identity",
      "Key ID": "Z40K1",
      "Names": [  ],
      "Markers": [Key marker,
        "Identity"
      ]
    }
  ],
  "Validator": 
}
{
  "Z1K1": "Z4",
  "Z4K1": "Z40",
  "Z4K2": ["Z3",
    {
      "Z1K1": "Z3",
      "Z3K1": "Z91",
      "Z3K2": "Z40K1",
      "Z3K3": [  ],
      "Z3K4": ["Z33",
        "Z34"
      ]
    }
  ],
  "Z4K3": 
}

In this case, the value for Z41/true would not change at all.

{
  "Type": "Boolean",
  "Identity": "True"
}
{
  "Z1K1": "Z40",
  "Z40K1": "Z41"
}

Required work

  • Add key marker type
  • Add identity value for the key marker type
  • Add key Z3K4 to Type Z3 / Key
  • Adapt types that use identity
  • Probably no specific changes to backend or frontend required

Advantages

  • No change to the instances that use identity
  • The out-of-the box interface for editing identity on a type would be quite neat
  • Easily extensible (see optional below)

Disadvantages

  • We would need to change type Z4/Type
    • But we just did that recently and it went quite smoothly

Optional: Optional

Optionality decides whether a key needs to be given or not. We have had optional keys since the beginning (e.g. Z14K2, Z60K2), but we have no way to mark them.

Proposal 3 and 4 would allow us to extend to represent optional keys quite straightforwardly, and we should probably do it since we are working on this anyway.

For Proposal 3, we would add another key, Z3K5/optional, with a Boolean type. If Z3K5 is set to true, that key is optional and does not have to be present on an instance of that type.

For Proposal 4, this would be even easier, since we would only need to create another instance of Z33/key marker. The default user experience for this would remain less cluttery.

One consideration is the question of what other markers there could be. I looked through all the types on Beta (99 types!), and that inspired me to a few more possible markers that could help e.g. with refining the user interface (e.g. a marker for making radio buttons, display checkboxes, etc.), maybe even mark mutability in the future, or indicate evaluation order. But it wasn’t a huge space of markers, I would say.

In any case, Validation would need to be adapted to take this new field into account. This can happen at a later time since we still control the creation of types, but would need to happen before we allow users to create types.

Out of scope

Eventually it would be nice to have different user experiences for selecting entities with different cardinalities. E.g. for Boolean, we have radio boxes between the two choices. For types that have a small number of instances, we could have a dropdown menu. For types with a large number of instances, we could use the type ahead / search entity selector. Designing and implementing these experiences is not part of this first work.