Context
If we have
data Foo = Foo { x :: Maybe Int, y :: Maybe Text }
we can already build it up applicative-style in an Applicative context (here IO) as
myfoo :: IO Foo
myfoo = Foo <$> getEnvInt "someX" <*> getEnvText "someY"
Problem
What if one prefers to build with explicitly writing out the record field names? Such as:
myfoo = Foo { x = getEnvInt "someX", y = getEnvText "someY" }
This won't typecheck. One solution is
{-# LANGUAGE RecordWildCards #-}
myfoo = do
x <- getEnvInt "someX"
y <- getEnvText "someY"
return $ Foo {..}
Which is not bad. But I wonder (at this point only for the sake of itself) 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 it can be done with bare GHC.Generics
pattern matching, but that wouldn't have type safety, so I was looking for a stronger approach. I encountered generics-sop
, which converts the record into a heterogeneous list, and comes with a seemingly handy hsequence
operation.
Point where I'm stuck
generics-sop
stores the Applicative's type in a separate type parameter of its heterogeneous list, and that is always I
(Identity) when using the generated conversion. So I would need to map the hlist and remove the I
from the elements which would effectively move the Applicative under I
to the mentioned type parameter (it would be Comp IO Maybe
), so I could use hsequence
, and finally add back the I
s so I can covert back to record.
But I don't know how to write a type signature for the I
removal / addition function, which communicates that the types of the respective hlist elements change consistently by losing/gaining the outer type. Is this even possible?
FooC { x = someEnvInt "someX" , y = someEnvText "someY" }
won't compile by itself. If you changesomeEnv___
to have signatureData.Functor.Compose IO Maybe ___
you might have a chance then. But at that point, I'm not sure it would be worth it at all anymore... – Shippinggenerics-sop
's equivalent) is acceptable. – Obstreperous(Applicative g, Applicative f) => FooC (Compose f g) -> f (FooC g)
(this function is essentially justsequence
) - then change the type ofsomeEnvInt
toCompose IO Maybe Int
. If you want you can do the 'uncomposition' using type families which would save you changing the type ofsomeEnvInt
but I personally don't think it's worth the effort. – Houlihan