-
Notifications
You must be signed in to change notification settings - Fork 77
UI design for inputting ADT values
XXX: There is a tool to do it: https://github.com/Zilliqa/scilla-json-utils. Perhaps we should implement it in Scilla.
This document proposes a UI for easy entry of Scilla ADT values, which are to be provided as inputs (in the form serialized JSONs) to Scilla. The document assumes that all primitive typed values (IntX
, String
, etc) are entered as plain text.
ADTs in Scilla allow building composite types. Defined below is simple ADT for illustration.
type MyAdt =
| MyConstructor1 of Int32 String
| MyConstructor2 of ByStr20
| MyConstructor3
An ADT is defined using one or more constructors (not to be confused with constructors from object-oriented languages). A value of type MyAdt
is constructed using exactly one of these constructors. In this example, a MyAdt
value constructed using MyConstructor1
will contain a tuple comprised of an Int32
value and a String
value. A MyAdt
value constructed using MyConstructor3
on the other hand will not contain any further values inside it.
The example we saw previously defines an ADT whose composite types are fixed. A polymorphic ADT allows using type variables in place of concrete constituent types, for substitution later. Let's see an example.
type 'A MyPAdt =
| MyPConstructor1 of 'A String
| MyPConstructor2 of ByStr20
| MyPConstructor3
MyPAdt
is a polymorphic ADT with a type parameter (variable) 'A
. When MyPAdt
is, for example, instantiated with the type parameter 'A
set to Int32
, then MyPAdt
will be the same as MyAdt
. List
, the Scilla built-in ADT is, for example, a polymorphic ADT. When we write List Int32
, it means that we have instantiated List
ADT with Int32
. This in-built ADT, if it wasn't in-built, would look like
type 'A List =
| Cons of 'A ('A List)
| Nil
Note: At the moment, Scilla does not support user-defined polymorphic ADTs. Only the in-built ADTs List
, Pair
and Option
are polymorphic. Users can define concrete ADTs such as MyAdt
above. This limitation is however not very relevant to this article. The article will assume support polymorphic ADTs.
While definitions of ADTs can be polymorphic, when a value is constructed the ADT must already be instantiated (specialized). So all ADT values have concrete (i.e., no type variables) types. This means that ADT values that are input by a user (for example, as a transition parameter) will have a concrete type.
To enable tools (such as IDEs) to work with ADT values of a Scilla contract, scilla-checker
, when provided the -contractinfo
flag prints a detailed definition of all ADTs (including in-build ADTs) in the contract. The ADT information for List
printed by scilla-checker
is shown below:
{
"tname": "List",
"tparams": [ "'A" ],
"tmap": [
{ "cname": "Cons", "argtypes": [ "'A", "List ('A)" ] },
{ "cname": "Nil", "argtypes": [] }
]
}
I suggest the reader to compare this information with the ADT definition of List
show above and ensure that the exact same information is present in both the descriptions.
With a brief background on ADTs in Scilla, let's now proceed to design a UI to input ADT values from users and build a JSON from it.
The concrete type of an ADT is known prior to parsing its value. For example, MyPAdt Int32
or List Int32
represent a concrete ADT. Given the ADT description from scilla-checker
, the IDE must first instantiate (specialize) this description, replacing the type variable (in this case 'A
) with the concrete type (Int32
here). Unexpectedly, the number of type arguments to a polymorphic ADT must be equal to the length of the tparams
field in the description JSON.
Below is a pseudo-code to input a Scilla value from the user, given its concrete type.
Json get_input(ConcreteType t) {
if (t is primitive type) {
text = get_input_plain_text();
return Json (text);
} else {
// Concrete ADT
string constructors_names[];
foreach (c in t.tmap) {
constructor_names.append(t.tmap.cname);
}
constructor = get_input_from_menu(constructor_names);
Json value;
value["constructor"] = constructor;
value["argtypes"] = t.tmap[constructor].argtypes;
foreach (t' in t.tmap[constructor].argtypes) {
value["arguments"].append(get_input(t'));
}
}
}
I'm not entirely sure of the layout to present to the user, but it will, in some form, be a dynamically growing tree layout. Maybe, the simplest layout would be to present an empty skeleton JSON, and fill up the JSON, using menus for constructors and plain text fields for primitive types. The tree will grow as the user inputs more data.
The result of this algorithm, for example, for a List Int64
type will look like:
{
"constructor": "Cons",
"argtypes": [
"Int64"
],
"arguments": [
"0",
{
"constructor": "Cons",
"argtypes": [
"Int64"
],
"arguments": [
"2",
{
"constructor": "Nil",
"argtypes": [
"Int64"
],
"arguments": []
}
]
}
]
}
This list contains [0,2]
. Note: While I treat List
as any other ADT here(which it is), we make an exception for Scilla List values. The elements of a List
can simply be specified as a JSON array. The IDE need not allow this exception though, to keep the UI uniform and simple. Scilla allows this exception because of the possibility of hand-written JSONs.