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
StudentRecordtype, including the data accessors, are part of the public interface of theBanner.StudentRecordmodule. In particular, we want the user to be able to create new student records using theSRdata constructor, to pattern match against it, and to updateStudentRecords using record update syntax. - A
StudentRecordneeds to satisfy some internal consistency conditions that aren't satisfied by every possible value constructible using theSRdata 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.StudentRecordmodule itself, record update syntax cannot be used to updateStudentRecords.
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.