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

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)

Li-*_*Xia 5

这是泛型的一个用例。

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