Haskell Export Read-Only Entry

I have a Haskell type that uses write syntax.

data Foo a = Foo { getDims :: (Int, Int), getData :: [a] } 

I do not want to export the constructor of the Foo value so that the user cannot create invalid objects. However, I would like to export getDims so that the user can get the dimensions of the data structure. If i do it

 module Data.ModuleName(Foo(getDims)) where 

then the user can use getDims to get the dimensions, but the problem is that they can also use the record update syntax to update the field.

 getDims foo -- This is allowed (as intended) foo { getDims = (999, 999) } -- But this is also allowed (not intended) 

I would like to prevent the latter, as it puts the data in an invalid state. I understand that I just can’t use the notes.

 data Foo a = Foo { getDims_ :: (Int, Int), getData :: [a] } getDims :: Foo a -> (Int, Int) getDims = getDims_ 

But this seems like a pretty roundabout way to solve the problem. Is there a way to continue using the syntax of the record by only exporting the record name for read access, and not for write access?

+5
source share
1 answer

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 #getDims $ moat (Foo (5,5) ['s']) (5,5) 

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.

+5
source

Source: https://habr.com/ru/post/1270821/


All Articles