Hiding the constructor and then defining new access functions for each field is a solution, but it can get tired for records with a large number of fields.
Here's a solution with the new HasField typeclass in GHC 8.2.1 , which avoids the need to define functions for each field.
The idea is to define an auxiliary new type, for example:
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE PolyKinds #-} -- Important, obscure errors happen without this. import GHC.Records (HasField(..)) -- Do NOT export the actual constructor! newtype Moat r = Moat r -- Export this instead. moat :: r -> Moat r moat = Moat -- If r has a field, Moat r also has that field instance HasField srv => HasField s (Moat r) v where getField (Moat r) = getField @sr
Each field in the r record will be accessible from Moat r with the following syntax:
Ξ» :set -XDataKinds Ξ» :set -XTypeApplications Ξ» getField @"getDims" $ moat (Foo (5,5) ['s']) (5,5)
The Foo constructor must be hidden from clients. However, field accessors for Foo should not be hidden; they must be available for HasField instances of Moat for input.
Each function of your open access api should return and receive Moat Foo instead of Foo s.
To make accessor syntax a little less verbose, we can go to OverloadedLabels :
import GHC.OverloadedLabels newtype Label rv = Label { field :: r -> v } instance HasField lrv => IsLabel l (Label rv) where fromLabel = Label (getField @l)
In ghci:
Ξ» :set -XOverloadedLabels Ξ» field
Instead of hiding the Foo constructor, another option is to make Foo fully public and define Moat inside your library by hiding any Moat constructors from clients.