我一直在努力使用每个可用的Generics库(EOT,SOP,Data / SYB和GHC.Generics)。我为每个库编写了一半代码样本,这些代码要么不编译,要么抛出运行时错误。
核心问题是:
type FieldName = String
type FieldValue = String
type MapType = [(String, String)] -- can be an actual HashMap as well, but doesn't really matter
data User = User {name :: String, email :: String}
data Post = User {title :: String, body :: String}
gFromMap :: MapType -> Maybe a
gToMap :: a -> MapType
-- the following should work
gFromMap [("name", "Saurabh"), ("email", "redacted@redacted.com")] :: User -- Just (User{..})
gFromMap [("title", "Will this work?"), ("body", "I hope it does!")] :: Post -- Just (Post{..})
gToMap User{name="Saurabh", email="redacted@redacted.com"} -- [("name", "Saurabh"), ("email", "redacted@redacted.com")]
gToMap Post{title="Will this work?", body="I hope it does!"} -- [("title", "Will this work?"), ("body", "I hope it does!)]
这是我使用Generics.EOT
编写的半编写的非编译代码:
import Generics.Eot
import Data.String.Conv
import Data.Text
newtype HStoreList = HStoreList [(Text, Text)] deriving (Eq, Show, Generic)
lookupHStore :: HStoreList -> Text -> Maybe Text
class FromHStoreList meta eot where
fromHStoreList :: meta -> HS.HStoreList -> eot
instance FromHStoreList Datatype (Either a Void) where
fromHStoreList dtype@Datatype{constructors=[Constructor{fields=(Selectors fields)}]} h = Left $ fromHStoreList fields h
fromHStoreList dtype@Datatype{constructors=[Constructor{fields=(NoSelectors _)}]} h = error $ "Target data type doesn't seem to have any record selectors, which is not supported: " ++ (show dtype)
fromHStoreList dtype@Datatype{constructors=[Constructor{fields=(NoFields)}]} h = error $ "Target data type doesn't seem to have any fields, which is not supported: " ++ (show dtype)
fromHStoreList dtype@Datatype{constructors=constr:_} h = error $ "Multiple constructors found, which is not supported: " ++ (show $ constructors dtype)
instance FromHStoreList [String] () where
fromHStoreList _ _ = ()
instance (FromHStoreList [String] xs) => FromHStoreList [String] (Maybe Text, xs) where
fromHStoreList [] h = error "shouldn't happen"
fromHStoreList (f:fs) h = (HS.lookupHStore h (toS f), fromHStoreList fs h)
这会产生以下编译错误:
185 99 error error:
• No instance for (FromHStoreList [String] a)
arising from a use of ‘fromHStoreList’
• In the second argument of ‘($)’, namely ‘fromHStoreList fields h’
In the expression: Left $ fromHStoreList fields h
In an equation for ‘fromHStoreList’:
fromHStoreList
dtype@(Datatype {constructors = [Constructor {fields = (Selectors fields)}]})
h
= Left $ fromHStoreList fields h (intero)
答案 0 :(得分:4)
这是GHC.Generics符合GHC.Generics tutorial
样式的解决方案首先,我们需要一些先决条件。 DefaultSignatures, DeriveGeneric, TypeOperators, FlexibleContexts
是GHC.Generics
的正常要求。这个特殊问题使用MultiParamTypeClasses
来确定哪些类型可以可靠地转换为字典,以及FlexibleInstances
(后面跟FlexibleContexts
}和TypeSynonymInstances
(因为我懒惰并输入String
几个地方。)
{-# LANGUAGE DeriveGeneric, DefaultSignatures, TypeOperators, MultiParamTypeClasses, FlexibleContexts, FlexibleInstances, TypeSynonymInstances #-}
import qualified Data.Map as Map
import GHC.Generics
为了让自己保持理智,我为可以序列化为字符串的事物添加了一个类。对于您的示例,我们只需要支持String
,但我怀疑您会很快遇到Bool
或Int
s。
任何时候我们反序列化(通常都会失败)跟踪错误很好,所以我们知道它失败的原因。
class Serializable a where
serialize :: a -> String
deserialize :: String -> Either String a
instance Serializable String where
serialize = id
deserialize = Right . id
GMapSerializable k
类表示可以在地图之间转换的通用表示类。从地图转换可能会失败。额外的k
参数表示序列化/反序列化工作所需的密钥类型,当前密钥显式传递给两个方法。
class GMapSerializable k f where
gFromMap :: k -> Map.Map String String -> Either String (f a)
gToMap :: k -> f a -> Map.Map String String
实例将使用String
密钥,如果他们需要知道在哪里读取或写入字段,或者将多态通过所有密钥。
为方便起见,我们还制作了相应的非泛型类。它使用()
表示密钥表示尚未提供密钥数据。
class MapSerializable a where
fromMap :: Map.Map String String -> Either String a
toMap :: a -> Map.Map String String
default fromMap :: (Generic a, GMapSerializable () (Rep a)) => Map.Map String String -> Either String a
fromMap map = to <$> gFromMap () map
default toMap :: (Generic a, GMapSerializable () (Rep a)) => a -> Map.Map String String
toMap x = gToMap () (from x)
对实例。我们将从一个好的K1 a
开始,类型a
的值保存在表示中的某个位置。为了将值转换为字典或从字典转换值,我们需要知道它应该存储或读取哪个密钥。 GMapSerializable String
实例要求传递String
密钥。
instance Serializable a => GMapSerializable String (K1 i a) where
gFromMap key map = K1 <$> (lookupE key map >>= deserialize)
gToMap key (K1 x) = Map.singleton key (serialize x)
lookupE :: (Ord k, Show k) => k -> Map.Map k v -> Either String v
lookupE k = maybe (Left $ "Key not found: " ++ show k) (Right) . Map.lookup k
当我们遇到选择器的元数据节点M1 S
并且元数据c
包含选择器名称时,将提供这些键。 fixProxy
是从selName
错误monad中获取Either String
的正确类型代理的黑客攻击。通常您将{/ 1}}传递给您正好拥有的整个selName
节点(或正在构建)。
M1
剩余的元数据节点,instance (Selector c, GMapSerializable String f) => GMapSerializable k (M1 S
c f) where
gFromMap _ map = fixProxy $ \proxy -> M1 <$> gFromMap (selName proxy) map
gToMap _ m@(M1 x) = gToMap (selName m) x
fixProxy :: (a -> f a) -> f a
fixProxy f = f undefined
用于数据类型,M1 D
用于构造函数,不关心他们处理的是哪种密钥。
M1 C
字典表示按键索引的许多值的产品。我们可以为两个值instance GMapSerializable k f => GMapSerializable k (M1 D c f) where
gFromMap key map = M1 <$> gFromMap key map
gToMap key (M1 x) = gToMap key x
instance GMapSerializable k f => GMapSerializable k (M1 C c f) where
gFromMap key map = M1 <$> gFromMap key map
gToMap key (M1 x) = gToMap key x
的产品提供GMapSerializable
个实例。转换为字典时,它会将每个部分转换为字典,并为每个部分提供字典的并集。从字典转换时,它会从同一个字典中构建每个部分,然后将这些部分组合到产品中。
f :*: g
我们还可以为单位instance (GMapSerializable k f, GMapSerializable k g) => GMapSerializable k (f :*: g) where
gFromMap key map = (:*:) <$> gFromMap key map <*> gFromMap key map
gToMap key (a :*: b) = Map.union (gToMap key a) (gToMap key b)
提供一个实例。它不需要从字典中读取任何内容 - 只有一个可能的值。它同样不需要在字典中写任何东西; U1
字典就足够了。
empty
我们显然不会为作曲或总和提供实例。组合将导致嵌套键,单个字典无法表示。总和将需要标记总和的分支;再一次,一个字典无法代表。
您的示例编译并运行,但由于使用了instance GMapSerializable k U1 where
gFromMap _ map = return U1
gToMap _ U1 = Map.empty
而不是键值对列表,因此存在细微差别。
数据类型派生Map
个实例,并通过Generic
实现获得MapSerializable
个实例。
default