wiki:ReadonlyConstructors

Version 3 (modified by nhn@…, 8 years ago) (diff)

--

read only constructors

when you export a data constructor, you export not only the ability to pattern match with said constructor, but also to create new values with that constructor. This can be harmful to abstract data types that want to make use of memoizing constructor or enforce invarients on a data type with constructor functions.

An example would be

data Term = LetRec {
        defns :: [(Name,Term)],
        body :: Term,
        freeVars :: [Name],
        sccDefns :: [[(Name,Term)]] }
        | Ap Term Term
        | Case ...

now, calculating the freevars and the strongly connected components are expensive operations so we would like them to be memoized in the constructor and only created if they are actually needed but also shared if they are needed again for the same type.

we can create a constructor to enforce this like so

letRec :: [(Name,Term)] -> Term -> Term
letRec defns body = LetRec {
        defns = defns,
        body = body,
        freeVars = fv body,
        sccDefns = calculateScc defns }

and that is good, but we cannot prevent people from getting around our constructor function without also blocking the very useful ability to pattern match on Terms.

proposal

allow data consructors to be exported and imported readonly

module Foo(bar,Foo(readonly Bar, Baz)) where

data Foo = Bar Int Int | Baz

-- enforce invarient that bars
-- second argument is always
-- twice the first one

bar i = Bar i (i*2)

we might want to reused the 'closed' keyword from the ClosedClasses proposal. see also the abstraction section in ExistingRecords.

Comment

This is related to Views and PatternSynonyms. There is definitely scope for improvement in this area, but somehow I think there should be a unified, more comprehensive story. This seems, to me, to be too much of a stop-gap measure.