Sha*_*iar 0 haskell functional-programming generic-programming
我有一个记录定义如下:
data MyData = MyData
{ name :: String
, addr :: String
... a lot of other fields of String type
}
Run Code Online (Sandbox Code Playgroud)
接下来我想创建对列表(String, fieldName),如下所示:
fields =
[ ("NAME", name)
, ("ADDRESS", addr)
, ... and for other fields
]
Run Code Online (Sandbox Code Playgroud)
最后,我需要一个函数,它可以获取类型的空记录MyData并逐个字段动态填充它,如下所示:
initByStrings strs = foldl (\ d (x, y) -> d{y=(findIn x strs)}) emptyMyData fields
Run Code Online (Sandbox Code Playgroud)
在 Haskell 中,如果没有像下面这样的长单调结构,这样的行为是否可能?
...
lst = map (\ x -> findIn x strs) fields
f lst where
f (name:addr:...) = MyData name addr ...
Run Code Online (Sandbox Code Playgroud)
这是泛型的一个用例。
import GHC.Generics
data MyData = MyData
{ ...
} deriving (Generic) -- extension: DerivingGeneric
Run Code Online (Sandbox Code Playgroud)
类型Generic类具有关联的类型Rep和方法to(和from)
to :: MyData -> Rep MyData p {- ignore the p -}
Run Code Online (Sandbox Code Playgroud)
Rep MyDataM1展开为由,(:*:)和构造的类型K1:
Rep MyData =
M1 D _ (
M1 C _ (
( M1 S _ (K1 _ String) )
:*:
( M1 S _ (K1 _ String) )
)
)
-- the things hidden by underscores carry metadata about MyData
-- (type name, constructor name, field names, whether fields are lazy, etc.).
Run Code Online (Sandbox Code Playgroud)
因此,如果您可以编写一个适用于M1, (:*:),的多种组合的函数,那么您就可以通过与 组合K1来获得 的函数。MyDatato
class GFromMap r where
gFromMap :: Map String String -> Maybe (r p) -- always ignore the p
-- extension: FlexibleContexts
fromMap :: (Generic a, GFromMap (Rep a)) => Map String String -> Maybe a
fromMap m = to <$> gFromMap m
Run Code Online (Sandbox Code Playgroud)
我们需要四个实例GFromMap。两个用于M1 D和newtypes ,它们携带我们不关心的M1 C信息(类型名称、构造函数名称)。MyData
-- extension: FlexibleInstances
instance GFromMap r => GFromMap (M1 D d r) where
gFromMap m = M1 <$> gFromMap m
instance GFromMap r => GFromMap (M1 C c r) where
gFromMap m = M1 <$> gFromMap m
Run Code Online (Sandbox Code Playgroud)
一件为产品(:*:)
-- extension: TypeOperators
instance (GFromMap r1, GFromMap r2) => GFromMap (r1 :*: r2) where
gFromMap m = (:*:) <$> gFromMap m <*> gFromMap m
Run Code Online (Sandbox Code Playgroud)
其中之一是字段,这里我们需要使用类型类从s与新类型关联的元数据中获取字段名称。M1 SSelector
-- extension: ScopedTypeVariables, TypeFamilies
-- the type equality (a ~ String) is for better error messages when
-- a record has a field not of type String
instance (a ~ String, Selector s) => GFromMap (M1 S s (K1 i a)) where
gFromMap m = M1 <$> K1 <$> Map.lookup fdName m
where fdName = toUpper <$> selName (undefined :: _t s _r _a) -- we can refer to s thanks to ScopedTypeVariables
Run Code Online (Sandbox Code Playgroud)
完整要点:https://gist.github.com/Lysxia/f27c078faec11487df2828cdfb81752a