Opaque Exports of Record Types
Consider the StudentRecord
type we defined earlier
module Banner.StudentRecord where
data StudentRecord = SR
{ bannerNumber :: BannerNumber
, name :: String
, address :: String
, transcript :: Transcript
}
newtype BannerNumber = B Int
data Transcript = T
Here, we place this type into a module Banner.StudentRecord
that is part of a
larger collection of modules that we use to implement the Banner system.
Now, a record type is a whole collection of things. First, it comes with data constructors just as any other algebraic data type, but it can also be "updated" using record update syntax and, possibly most importantly, the field names act as data accessors. Here, we have functions
bannerNumber :: StudentRecord -> BannerNumber
name :: StudentRecord -> String
address :: StudentRecord -> String
transcript :: StudentRecord -> Transcript
When exporting the StudentRecord
type, we now have the usual choices:
- The internals of the
StudentRecord
type, including the data accessors, are part of the public interface of theBanner.StudentRecord
module. In particular, we want the user to be able to create new student records using theSR
data constructor, to pattern match against it, and to updateStudentRecord
s using record update syntax. - A
StudentRecord
needs to satisfy some internal consistency conditions that aren't satisfied by every possible value constructible using theSR
data constructor. In this case, we want to export the type opaquely, hiding its data constructors. This opaque export means that the data constructors and data accessors are not exported. As a consequence, outside of theBanner.StudentRecord
module itself, record update syntax cannot be used to updateStudentRecord
s.
Again, we use StudentRecord(..)
in the export list of our module if we want to
export StudentRecord
transparently, and only StudentRecord
if we want to
export StudentRecord
opaquely.
When exporting StudentRecord
opaquely, we have the option to expose all or
some of the data accessors. Remember, the main reason to export a type opaquely
is to prevent the creation of values of this type that are invalid in some
sense. The data accessors cannot be used to create such values; for that, we
would need the data constructors of the type. In some cases, it makes sense to
consider all or some of the data accessors as part of the public interface.
Here, for example, it makes perfect sense to export functions that allows us to
extract the banner number, name, address or transcript from a StudentRecord
.
To export StudentRecord
opaquely but export the data accessors, we'd use the
export list
module Banner.StudentRecord
( StudentRecord
, BannerNumber(..)
, Transcript(..)
, bannerNumber
, name
, address
, transcript
) where
We're explicitly exporting bannerNumber
, name
, address
, and transcript
along with the opaque type StudentRecord
. To the user of the
Banner.StudentRecord
module, bannerNumber
, name
, address
, and
transcript
look like ordinary functions. They have no indication whatsoever
that these are in fact data accessors created by the definition of
StudentRecord
as a record type.