Implement the applicative builder style with Generics

Context

If we have

data Foo = Foo { x :: Maybe Int, y :: Maybe Text } 

we can already create an applicative style in an applicative context (here IO) as

 myfoo :: IO Foo myfoo = Foo <$> getEnvInt "someX" <*> getEnvText "someY" 

Problem

What if someone prefers to build with explicit spelling of record field names? For example:

 myfoo = Foo { x = getEnvInt "someX", y = getEnvText "someY" } 

It will not look. One solution is

 {-# LANGUAGE RecordWildCards #-} myfoo = do x <- getEnvInt "someX" y <- getEnvText "someY" return $ Foo {..} 

It's not bad. But I wonder (for now, just for my own sake) if the following could work:

 data FooC f = FooC { x :: f Int, y :: f Text } type Foo = FooC Maybe myfoo :: IO Foo myfoo = genericsMagic $ FooC { x = someEnvInt "someX" , y = someEnvText "someY" } 

I believe that this can be done with a simple GHC.Generics pattern GHC.Generics , but this will not have type safety, so I was looking for a stronger approach. I came across generics-sop , which converts a record to a heterogeneous list and comes with a seemingly convenient hsequence operation.

Point where I'm stuck

generics-sop stores the Applicative type in a separate type parameter of its heterogeneous list, and this is always I (Identity) when using the generated transformation. So I would need to display the hlist and remove I from the elements that could effectively move the applicator under I to the specified type parameter (that would be Comp IO Maybe ), so I could use hsequence , and finally return I so that I can discreetly return to the recording.

But I do not know how to write a type signature for the delete / add function I , which reports that the types of the corresponding hlist elements are sequentially changed by losing / obtaining the external type. Is it possible?

+7
haskell ghc-generics
source share
2 answers

But I don’t know how to write a type signature to remove I / add a function that reports that the types of the corresponding hlist elements are sequentially changed by losing / getting the external type. Is it possible?

I don’t know how to do it either. A possible workaround (at the cost of some template) would be to use synonyms for record templates to directly build the presentation of the sum of goods, the ability to use the named fields:

 {-# language DeriveGeneric #-} {-# language TypeFamilies #-} {-# language TypeOperators #-} {-# language PatternSynonyms #-} import Data.Text import qualified GHC.Generics as GHC import Generics.SOP import Text.Read data Foo = Foo { x :: Int, y :: Text } deriving (Show, GHC.Generic) instance Generic Foo pattern Foo' :: t Int -> t Text -> SOP t (Code Foo) pattern Foo' {x', y'} = SOP (Z (x' :* y' :* Nil)) readFooMaybe :: SOP (IO :.: Maybe) (Code Foo) readFooMaybe = Foo' { x' = Comp (fmap readMaybe getLine) , y' = Comp (fmap readMaybe getLine) } 

Ghci testing:

 ghci> hsequence' readFooMaybe >>= print 12 "foo" SOP (Z (Just 12 :* (Just "foo" :* Nil))) 
0
source share

The problem with Generics is that your FooC type has the form (* -> *) -> * and, as far as I know, it is not possible to automatically get an instance of GHC.Generics for this type. If you are open to a solution using the Haskell pattern, it is relatively easy to write the TH code needed to automatically process any type of record.

 {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TemplateHaskell #-} module AppCon where import Control.Applicative import Control.Compose ((:.), unO) import Language.Haskell.TH class AppCon t where appCon :: Applicative f => t (f :. g) -> f (tg) deriveAppCon :: Name -> Q [Dec] deriveAppCon name = do (TyConI (DataD _ _ _ _ [RecC con fields] _)) <- reify name let names = [mkName (nameBase n) | (n,_,_) <- fields] apps = go [|pure $(conE con)|] [[|unO $(varE n)|] | n <- names] where go l [] = l go l (r:rs) = go [|$l <*> $r|] rs [d|instance AppCon $(conT name) where appCon ($(conP con (map varP names))) = $apps |] 

I use the type composition operator from the TypeCompose package to define a class type that can "expand" one application layer from a record type. If you have FooC (IO :. Maybe) , you can turn it into IO (FooC Maybe) .

deriveAppCon allows deriveAppCon to automatically output an instance for any base record type.

 {-# LANGUAGE TemplateHaskell #-} import Control.Compose ((:.)(..)) import AppCon data FooC f = FooC { x :: f Int, y :: f Text } type Foo = FooC Maybe deriveAppCon ''FooC myfoo :: IO Foo myfoo = appCon $ FooC { x = O $ someEnvInt "someX" , y = O $ someEnvText "someY" } 

The O constructor from TypeCompose used to transfer the result of the function IO (Maybe a) to compound ((IO .: Maybe) a) .

0
source share

All Articles