[英]Implement Applicative builder style with Generics
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 我们已经可以在Applicative上下文(这里是IO)中将它构建为applicative-style
myfoo :: IO Foo
myfoo = Foo <$> getEnvInt "someX" <*> getEnvText "someY"
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. 我相信它可以通过裸
GHC.Generics
模式匹配来完成,但是它不具有类型安全性,所以我一直在寻找更强大的方法。 I encountered generics-sop
, which converts the record into a heterogeneous list, and comes with a seemingly handy hsequence
operation. 我遇到了
generics-sop
,它将记录转换为异构列表,并带来了看似方便的hsequence
操作。
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. generics-sop
将Applicative的类型存储在其异构列表的单独类型参数中,并且在使用生成的转换时始终为I
(Identity)。 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. 所以,我需要映射hlist并删除
I
从中会有效地移动应用型下的元素I
所提到的类型参数(这将是Comp IO Maybe
),这样我就可以使用hsequence
,最后加回I
小号所以我可以转回记录。
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. 但我不知道如何为
I
删除/添加功能编写类型签名,该功能通过丢失/获取外部类型来传达各个hlist元素的类型一致地改变。 Is this even possible? 这甚至可能吗?
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.
但我不知道如何为I删除/添加功能编写类型签名,该功能通过丢失/获取外部类型来传达各个hlist元素的类型一致地改变。 Is this even possible?
这甚至可能吗?
I don't know how to do that either. 我也不知道怎么做。 A possible workaround (at the cost of some boilerplate) would be to use record pattern synonyms to construct the sum-of-products representation directly, while still being able to use 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)
}
Testing it on ghci: 在ghci上测试它:
ghci> hsequence' readFooMaybe >>= print
12
"foo"
SOP (Z (Just 12 :* (Just "foo" :* Nil)))
The problem with Generics is that your FooC
type has the kind (* -> *) -> *
and, as far as I know, it's not possible to automatically derive a GHC.Generics
instance for such a type. Generics的问题是你的
FooC
类型有类型(* -> *) -> *
,据我所知,不可能为这种类型自动派生GHC.Generics
实例。 If you are open to a solution using Template Haskell it's relatively easy to write the TH code needed to automatically handle any record type. 如果您对使用Template Haskell的解决方案持开放态度,则编写自动处理任何记录类型所需的TH代码相对容易。
{-# 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 (t g)
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 type-class that can "unwrap" a single applicative layer from a record type. 我使用
TypeCompose
包中的类型组合运算符来定义一个类型类,它可以从记录类型“解包”单个应用层。 Ie if you have a FooC (IO :. Maybe)
you can turn it into a IO (FooC Maybe)
. 即如果你有一个
FooC (IO :. Maybe)
你可以把它变成一个IO (FooC Maybe)
。
The deriveAppCon
lets you automatically derive an instance for any basic record type. deriveAppCon
允许您自动派生任何基本记录类型的实例。
{-# 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
is used to wrap the function result IO (Maybe a)
into a composite ((IO .: Maybe) a)
. TypeCompose
的O
构造函数用于将函数结果IO (Maybe a)
包装到复合((IO .: Maybe) a)
TypeCompose
((IO .: Maybe) a)
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.