记录动态解析字段名称的更新

Record update for dynamically-resolved field name

我有如下定义的记录:

data MyData = MyData
    { name :: String
    , addr :: String
     ... a lot of other fields of String type
    }

接下来我想创建成对列表 (String, fieldName),像这样:

fields =
  [ ("NAME", name)
  , ("ADDRESS", addr)
  , ... and for other fields
  ]

最后我需要一个函数,它可以获取 MyData 类型的空记录并逐字段动态填充它,如下所示:

initByStrings strs = foldl (\ d (x, y) -> d{y=(findIn x strs)}) emptyMyData fields 

如果没有像下面这样的长单调结构,在 Haskell 中是否可能出现这样的行为?

...
lst = map (\ x -> findIn x strs) fields
f lst where
    f (name:addr:...) = MyData name addr ...

解决方案是这样创建的:

字段列表具有更新记录中相应字段的功能:

fields =
  [ ("NAME", (\d x -> d{name=x}))
  , ("ADDRESS", (\d x -> d{addr=x}))
  , ... and for other fields
  ]

初始化 MyData 记录的函数如下所示:

 initByStrings strs = foldl (\ d (x, y) -> y d(findIn x strs)}) emptyMyData fields 

因此记录字段可以从 foldl 使用一些外部函数从列表中字段的字符串名称解析器字符串值一一更新。

这是泛型的一个用例。

import GHC.Generics

data MyData = MyData
  { ...
  } deriving (Generic)  -- extension: DerivingGeneric

Generic 类型 class 有一个关联类型 Rep 和一个方法 to(和 from

to :: MyData -> Rep MyData p {- ignore the p -}

Rep MyData 展开为由 M1(:*:)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.).

因此,如果您可以编写适用于 M1(:*:)K1 的多种组合的函数,则可以通过组合获得 MyData 上的函数to.

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

我们需要 GFromMap 的四个实例。两个 M1 DM1 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

一个用于产品 (:*:)

-- extension: TypeOperators
instance (GFromMap r1, GFromMap r2) => GFromMap (r1 :*: r2) where
  gFromMap m = (:*:) <$> gFromMap m <*> gFromMap m

还有一个用于字段,这里我们需要使用 Selector 类型 class 从与 M1 S 新类型关联的元数据 s 中获取字段名称。

-- 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

完整要点:https://gist.github.com/Lysxia/f27c078faec11487df2828cdfb81752a