Haskell 根据字段名称字符串动态设置记录字段? [英] Haskell dynamically set record field based on field name string?
问题描述
假设我有以下记录:
data Rec = Rec {
field1 :: Int,
field2 :: Int
}
我如何编写函数:
changeField :: Rec -> String -> Int -> Rec
changeField rec fieldName value
这样我就可以将字符串field1"或field2"传入fieldName
参数并让它更新关联的字段?我知道 Data.Data
和 Data.Typeable
是在这里使用的,但我无法弄清楚这两个包.
such that I can pass in the strings "field1" or "field2" into the fieldName
argument and have it update the associated field? I understand Data.Data
and Data.Typeable
are what to use here but I can't figure these two packages out.
我见过的一个库的例子是 cmdArgs.下面是一篇关于如何使用这个库的博客文章的摘录:
An example of a library I've seen do this is cmdArgs. Below is an excerpt from a blog posting on how to use this library:
{-# LANGUAGE DeriveDataTypeable #-}
import System.Console.CmdArgs
data Guess = Guess {min :: Int, max :: Int, limit :: Maybe Int} deriving (Data,Typeable,Show)
main = do
x <- cmdArgs $ Guess 1 100 Nothing
print x
现在我们有了一个简单的命令行解析器.一些示例交互是:
Now we have a simple command line parser. Some sample interactions are:
$ guess --min=10
NumberGuess {min = 10, max = 100, limit = Nothing}
推荐答案
好的,这里有一个不使用模板 haskell 的解决方案,也不需要您手动管理字段映射.
OK, here's a solution that doesn't use template haskell, or require you to manage the field map manually.
我实现了一个更通用的 modifyField
,它接受一个 mutator 函数,并实现了 setField
(nee changeField
) 和 const 一起使用价值
.
I implemented a more general modifyField
which accepts a mutator function, and implemented setField
(nee changeField
) using it with const value
.
modifyField
和 setField
的签名在记录和 mutator/value 类型中都是通用的;但是,为了避免 Num
的歧义,调用示例中的数字常量必须具有明确的 :: Int
签名.
The signature of modifyField
and setField
is generic in both the record and mutator/value type; however, in order to avoid Num
ambiguity, the numeric constants in the invocation example have to be given explicit :: Int
signatures.
我还改变了参数顺序,所以 rec
排在最后,允许通过正常的函数组合创建一个 modifyField
/setField
链(见最后一个调用示例).
I also changed the parameter order so rec
comes last, allowing a chain of modifyField
/setField
to be created by normal function composition (see the last invocation example).
modifyField
建立在原始 gmapTi
之上,它是 Data.Data
中的一个缺失"函数.它是 gmapT
和 gmapQi
之间的交叉.
modifyField
is built on top of the primitive gmapTi
, which is a 'missing' function from Data.Data
. It is a cross between gmapT
and gmapQi
.
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RankNTypes #-}
import Data.Typeable (Typeable, typeOf)
import Data.Data (Data, gfoldl, gmapQi, ConstrRep(AlgConstr),
toConstr, constrRep, constrFields)
import Data.Generics (extT, extQ)
import Data.List (elemIndex)
import Control.Arrow ((&&&))
data Rec = Rec {
field1 :: Int,
field2 :: String
} deriving(Show, Data, Typeable)
main = do
let r = Rec { field1 = 1, field2 = "hello" }
print r
let r' = setField "field1" (10 :: Int) r
print r'
let r'' = setField "field2" "world" r'
print r''
print . modifyField "field1" (succ :: Int -> Int) . setField "field2" "there" $ r
print (getField "field2" r' :: String)
---------------------------------------------------------------------------------------
data Ti a = Ti Int a
gmapTi :: Data a => Int -> (forall b. Data b => b -> b) -> a -> a
gmapTi i f x = case gfoldl k z x of { Ti _ a -> a }
where
k :: Data d => Ti (d->b) -> d -> Ti b
k (Ti i' c) a = Ti (i'+1) (if i==i' then c (f a) else c a)
z :: g -> Ti g
z = Ti 0
---------------------------------------------------------------------------------------
fieldNames :: (Data r) => r -> [String]
fieldNames rec =
case (constrRep &&& constrFields) $ toConstr rec of
(AlgConstr _, fs) | not $ null fs -> fs
otherwise -> error "Not a record type"
fieldIndex :: (Data r) => String -> r -> Int
fieldIndex fieldName rec =
case fieldName `elemIndex` fieldNames rec of
Just i -> i
Nothing -> error $ "No such field: " ++ fieldName
modifyField :: (Data r, Typeable v) => String -> (v -> v) -> r -> r
modifyField fieldName m rec = gmapTi i (e `extT` m) rec
where
i = fieldName `fieldIndex` rec
e x = error $ "Type mismatch: " ++ fieldName ++
" :: " ++ (show . typeOf $ x) ++
", not " ++ (show . typeOf $ m undefined)
setField :: (Data r, Typeable v) => String -> v -> r -> r
setField fieldName value = modifyField fieldName (const value)
getField :: (Data r, Typeable v) => String -> r -> v
getField fieldName rec = gmapQi i (e `extQ` id) rec
where
i = fieldName `fieldIndex` rec
e x = error $ "Type mismatch: " ++ fieldName ++
" :: " ++ (show . typeOf $ x) ++
", not " ++ (show . typeOf $ e undefined)
这篇关于Haskell 根据字段名称字符串动态设置记录字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!