Breaking encapsulation in a controlled way using module signatures

Hi, I wanted to share this small experiment about using Backpack module signatures to manage encapsulation “levels” for abstract datatypes.

The basic motivation is the following: we often want abstract datatypes which hide their internals from the prying eyes of other modules. But we also want to be able to inspect the internal structure of values for logging or debugging purposes. How to reconcile the two aims?

In my experiment, the program logic depends on a module signature which defines a Mystery type constructor with very few operations. The “inspection” function for datatypes returns Strings wrapped in this Mystery constructor. Other functions in the program logic can’t do anything with these wrapped values.

However, “framework” -like code gives a concrete implementation to the Mystery type constructor, meaning that it can can do useful things with the result of “inspection” functions.

I’m left with a question: can something similar to this be achieved without Backpack?

I think the standard Haskell way to do this is to make a *.Internal module which exposes the internals and then the normal module re-exports only the public interface. I have not read your implementation in detail, but it sounds very similar. I assume you are also aware of that, so can you summarize how it is different or why such a Internal module is not good enough?

I wanted to hide the internals of datatypes from other modules of the same library, and expose the internals to “framework” code outside the library. The *.Internal pattern can help with the second but not with the first: nothing would stop modules from working with the internals of datatypes declared in other modules.

Admittedly, such strong inter-module encapsulation is not very frequent in Haskell.