Is there a way to apply Maybe constructor to each field of record with generics?

前端 未结 2 1494
情深已故
情深已故 2021-01-19 10:33

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 ::          


        
相关标签:
2条回答
  • 2021-01-19 11:15

    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.

    0 讨论(0)
  • 2021-01-19 11:29

    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.

    0 讨论(0)
提交回复
热议问题