Home > Enterprise >  How to refactor a Haskell data type with fields into a tagged union of records?
How to refactor a Haskell data type with fields into a tagged union of records?

Time:09-29

In a large code base, I want to refactor a data type that has constructors with fields into a simple tagged union of records, mainly to better support -Wincomplete-record-updates. It is important that I do not need to change any pattern matches over this data type, so I am trying to use record pattern synonyms. Here is a cut-down version:

{-# LANGUAGE PatternSynonyms #-}

{-# OPTIONS_GHC -Werror=incomplete-patterns #-}

-- Consider some data type with fields:

data D'
  = A'
  | B' { b'Int :: Int, b'String :: String }

foo' :: D' -> String
foo' A'{} = "A"
foo' B'{b'String = x} = x

-- I'd like to factor out a record containing the fields
-- without changing foo'.

data D
  = A
  | BTag BRec

data BRec = BRec { _bInt :: Int, _bString :: String }

pattern B :: Int -> String -> D
pattern B{ bInt, bString } = BTag (BRec bInt bString)

foo :: D -> String
foo A{} = "A"
foo B{bString = x} = x

-- I get this error:
--     Pattern match(es) are non-exhaustive
--     In an equation for ‘foo’: Patterns of type ‘D’ not matched: BTag _
--    |
-- 28 | foo A{} = "A"
--    | ^^^^^^^^^^^^^...

Why do I get a pattern coverage error? Am I using pattern synonyms correctly? How to get the refactoring that i want?

CodePudding user response:

You should use a COMPLETE pragma:

{-# COMPLETE A, B #-}

That can go anywhere in the file, but as an organizational thing it's useful to put it by the pattern definition. It's worth note that you can define multiple COMPLETE pragmas for the same type if there are multiple different sets that cover all possibilities. For instance, Data.Sequence.Seq has two:

{-# COMPLETE (:<|), Empty #-}
{-# COMPLETE (:|>), Empty #-}

This allows you plenty of flexibility in providing overlapping sets of synonyms that can be complete in different ways.

As for a justification of why it's necessary, this article on the GHC wiki makes a good argument:

Pattern synonyms are a means of abstraction, if the exhaustiveness checker could look through a definition then the implementation of P would leak into error messages. We want users to be able to replace bona-fide data constructors with pattern synonyms without consumers noticing.

  • Related