The predefined Mythryl sumtype Exception is unique in that it may be incrementally extended by defining new constructors for it using exception declarations.
Vanilla Mythryl sumtype declarations require that all constructors belonging to the type be declared up front at the point of sumtype definition; no later extension of the sumtype is allowed. Normally this is good; it means that when you read a sumtype definition in the code you can be sure that what you see is the complete story.
But situations can occasionally arise in industrial-scale Mythryl programming in which it is desirable to incrementally extend a sumtype.
Programmers can and do simply use the Exception sumtype directly in such cases, defining new constructors as needed via exception declarations.
Sometimes, however, it is better to be a little more typesafe by keeping such constructors type-distinct from vanilla Exception constructors.
Here is a hack to define your own extensible sumtypes separate from the standard Mythryl Exception sumtype:
#!/usr/bin/mythryl # Each application of api "Extensible" to # package "extensible" generates a new # package exporting a new type "Extensible": api Extensible { Extensible; make_new_constructor_deconstructor_pair: Null_Or(X) -> ( (X -> Extensible), (Extensible -> Null_Or(X)) ); }; package extensible { Extensible = Exception; fun make_new_constructor_deconstructor_pair _ = { exception CONSTRUCTOR(X); fun deconstructor (CONSTRUCTOR(y)) => THE y; deconstructor _ => NULL; end; (CONSTRUCTOR, deconstructor); }; }; # Define two new extensible types, # Extensible1 and Extensible2: # package extensible1 = extensible: Extensible; Extensible1 = extensible1::Extensible; # First new extensible type. # package extensible2 = extensible: Extensible; Extensible2 = extensible2::Extensible; # Second new extensible type. # Define two new constructor/deconstructor pairs # for each of our new extensible types: # my (constructor1a, deconstructor1a) = extensible1::make_new_constructor_deconstructor_pair( NULL: Null_Or(Int) ); my (constructor1b, deconstructor1b) = extensible1::make_new_constructor_deconstructor_pair( NULL: Null_Or(String) ); # my (constructor2a, deconstructor2a) = extensible2::make_new_constructor_deconstructor_pair( NULL: Null_Or(Int) ); my (constructor2b, deconstructor2b) = extensible2::make_new_constructor_deconstructor_pair( NULL: Null_Or(String) ); # Apply all four of our new constructors: # wrapped1a = constructor1a( 1112 ); wrapped1b = constructor1b( "food" ); # wrapped2a = constructor2a( 2111 ); wrapped2b = constructor2b( "foof" ); # Apply all four of our new destructors, # recovering the wrapped values: # unwrapped1a = the (deconstructor1a wrapped1a); unwrapped1b = the (deconstructor1b wrapped1b); unwrapped2a = the (deconstructor2a wrapped2a); unwrapped2b = the (deconstructor2b wrapped2b); # Display our recovered results # to the cheering crowd: # printf "unwrapped1a == %d\n" unwrapped1a; printf "unwrapped1b == %s\n" unwrapped1b; # printf "unwrapped2a == %d\n" unwrapped2a; printf "unwrapped2b == %s\n" unwrapped2b;
Running this produces:
linux$ ./my-script unwrapped1a == 1112 unwrapped1b == food unwrapped2a == 2111 unwrapped2b == foof
This is somewhat clumsy. Whether that is a bug or a feature depends on whether you believe the use of extensible types should be encouraged or discouraged.
The above construction also has some technical limitations.
As presented, it does not allow creation of 0-ary constructors. This can be circumvented by (say) adding an extra make_new_0ary_constructor call.
More importantly, it does not allow creating parameterized extensible types.
Credit: The above construction is adapted from Bernard Berthomieu’s March 2000 OO Programming Styles in ML paper. The core technique has been in general circulation for some time.
For a production example of this technique in action see src/lib/src/property-list.pkg.