I have two data types and the second one is the copy of first, but with Maybe on each field.
data A = {a :: Int, b :: String}
data B = {c :: Maybe Int, d ::
How about:
{-# LANGUAGE RankNTypes #-}
data R f = R { a :: f Int, b :: f String, c :: f Char }
newtype I a = I { unI :: a }
fromMaybeI :: I a -> Maybe a -> I a
fromMaybeI a Nothing = a
fromMaybeI _ (Just a) = I a
fromMaybeR :: R I -> R Maybe -> R I
fromMaybeR ri rm =
R (go a) (go b) (go c)
where
go :: (forall f. R f -> f a) -> I a
go x = fromMaybeI (x ri) (x rm)
R Maybe is the record with Maybe values, R I is the record with concrete values.
Using RankNTypes reduces the amount of boilerplate code in fromMaybeR.
One downside is that you have use I and unI to construct and
access the field values.
This can be done with generics-sop, a library that extends the default Generics machinery of GHC.
"generics-sop" can take a regular record and deduce a generic representation for it. This representation has a type parameter that wraps every field, and the library allows Applicative sequence-like operations across the record fields.
{-# language TypeOperators #-}
{-# language DeriveGeneric #-}
{-# language TypeFamilies #-}
{-# language DataKinds #-}
import qualified GHC.Generics as GHC
import Generics.SOP
data A = A {a :: Int, b :: String} deriving (Show,GHC.Generic)
instance Generic A -- this Generic is from generics-sop
defaulty :: (Generic a, Code a ~ '[ xs ]) => NP Maybe xs -> a -> a
defaulty maybes r = case (from r) of
SOP (Z np) -> let result = hliftA2 (\m i -> maybe i I m) maybes np
in to (SOP (Z result))
main :: IO ()
main = do
print $ defaulty (Nothing :* Just "bar" :* Nil) (A 99 "foo")
Nothing :* Just "bar" :* Nil is a generic representation that matches the list of fields in the original record definition. Notice that each field in the representation is wrapped in Maybe.
See here for another example of generics-sop.