Casting a nullary data constructor between types

Suppose I have a parametrized data type with more than one nullary (constant) data constructor, such as:

data Check a = Valid | Invalid | Unsure a

Sometimes I want to manipulate the non-constant constructors with a function that keeps the nullary constants fixed, and I hope to do it like

instance Functor Check where
  fmap f (Unsure a) = Unsure (f a)
  fmap f c = c

This fails to typecheck because in the declaration fmap f c = c, if f :: a -> b then c needs to be of both types Check a and Check b. Of course I could just write it out fully into

  fmap f (Unsure a) = Unsure (f a)
  fmap f Valid = Valid
  fmap f Invalid = Invalid

which works but gets very unwieldy when there are many nullary constructors. Or, in some cases I could probably derive instances of Show and Read and use read (show c) to coerce c to the necessary type. But is there a more elegant way to approach this?


One thing you could do is change the definition of your datatype to factour out the Valid / Invalid part like so:

data Status  = StatusValid | StatusInvalid
data Check a = Sure Status | Unsure a

You can then implement Functor without having to inspect the Status:

instance Functor Check where
  fmap f (Unsure a) = Unsure (f a)
  fmap _ (Sure s)   = Sure s

If you are willing to use the PatternSynonyms extension (just stick {-# LANGUAGE PatternSynonyms #-} at the top of your file), you can even recover Valid / Invalid:

pattern Valid   = Sure StatusValid
pattern Invalid = Sure StatusInvalid

You can use the DeriveFunctor extension and just write

data Check a = Valid | Invalid | Unsure a deriving Functor

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *