We have previously discussed casting a package to an API so as to achieve implementation hiding by removing from view all package elements not declared in the API.
That kind of package casting is usually called strong sealing in the functional programming literature.
Recent research has established there to be a spectrum of plausible and in fact useful forms of package casting. At present Mythryl implements two:
Strong package casting verifies that the package declaration is consistent with the api declaration — for example that all required functions are present and of the right type — and then hides from external view all remaining package elements. Also, types declared as opaque in the api
My_Type;
become opaque to the outside world under strong casting. Under Mythryl typing rules, this also makes them new types, different from all previously declared types.
Strong package casting may be included in your original package definition:
#!/usr/bin/mythryl api My_Api { My_Type; print_it: My_Type -> Void; }; package my_root_package: My_Api { My_Type = String; fun private_print_fn string = printf "My_Type value == '%s'\n" string; fun print_it string = private_print_fn string; };
Here we have cast my_package to My_Api in order to hide both the structure of My_Type and also the helper function private_print_fn. The external world sees only an opaque type My_Type and a function print_it which operates upon that type. This means that changes in the definition of My_Type cannot possibly break external code — which is what makes such package casting so useful in the design and implementation of large software systems.
We can also cast a package after the fact with an API as a separate operation from package definition:
#!/usr/bin/mythryl api My_Api { My_Type; print_it: My_Type -> Void; }; package my_root_package { My_Type = String; fun private_print_fn string = printf "My_Type value == '%s'\n" string; fun print_it string = private_print_fn string; }; package my_cast_package = my_root_package: My_Api;
This may seem uselessly obtuse at first blush: Why not just do the package casting up front and be done with it? But consider this example:
#!/usr/bin/mythryl api My_First_Api { My_Type; print_it: My_Type -> Void; }; api My_Second_Api { My_Type; print_fn: My_Type -> Void; }; package my_root_package { My_Type = THIS( String ) | THAT( Int ) ; fun private_print_fn string = printf "My_Type value == '%s'\n" string; fun print_it string = private_print_fn string; fun print_fn string = private_print_fn string; }; package my_first_package = my_root_package: My_First_Api; package my_second_package = my_root_package: My_Second_Api;
Here we have generated two different externally visible packages from a single root package definition by casting it to two different api definitions. In a small tutorial example this looks silly, but this can become a real asset in the context of a large software development project where apis are defined and implemented by multiple groups and a given module may need to implement multiple externally provided apis.
Now we can begin to understand why the modern package casting approach is more powerful than the older technique of scattering public and private keywords all through the package definition: Aside from achieving better separation of concerns by divorcing package definition from API definition, the package casting approach defines a package algebra in which package definitions provide the seed values and API definitions provide the functions which produce new values from old.
Package casting literally gives us an entirely new language for programming in the large.
Weak package casting is an older form of package casting which allows as much as possible of the original type equality information to propagate through to the resulting package interface. This form was developed first and is at this point present for primarily historical reasons.
We designate weak package casting by adding a (weak) qualifier after the casting colon:
#!/usr/bin/mythryl api My_Api { My_Type; print_it: My_Type -> Void; }; package my_package: (weak) My_Api { My_Type = String; fun private_print_fn string = printf "My_Type value == '%s'\n" string; fun print_it string = private_print_fn string; };
This version leaves visible the maximal possible amount of equality information about My_Type, allowing it to still be externally equal to type String, while still protecting the internal private_print_fn function frome external access.
Sometimes this additional propagation of type equality information is just what you need. Strong package casting is the default and normal case, but having both strong and weak package casting available makes Mythryl more expressive for programming in the large.