说我有以下记录:
data Rec = Rec {
field1 :: Int,
field2 :: Int
}
我该如何编写这个函数:
changeField :: Rec -> String -> Int -> Rec
changeField rec fieldName value
这样我就可以传入字符串" field1"或" field2"进入fieldName
参数并让它更新相关字段?我了解Data.Data
和Data.Typeable
是在这里使用的内容,但我无法解决这两个问题。
我见过的图书馆的一个例子是cmdArgs。以下是有关如何使用此库的博客文章中的excerpt:
{-# 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
现在我们有一个简单的命令行解析器。一些示例交互是:
$ guess --min=10
NumberGuess {min = 10, max = 100, limit = Nothing}
答案 0 :(得分:8)
好的,这是一个不使用模板haskell的解决方案,或者需要您手动管理字段映射。
我实施了一个更通用的modifyField
,它接受了一个mutator函数,并使用setField
实现了changeField
(nee const value
)。
modifyField
和setField
的签名在记录和mutator / value类型中都是通用的;但是,为了避免Num
歧义,调用示例中的数字常量必须给出明确的:: Int
签名。
我还更改了参数顺序,因此rec
位于最后,允许通过正常的函数组合创建modifyField
/ setField
链(请参阅上一个调用示例)。
modifyField
建立在原始gmapTi
之上,这是来自Data.Data
的'缺失'功能。它是gmapT
和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)
答案 1 :(得分:6)
您可以从字段名称到镜头构建地图:
{-# LANGUAGE TemplateHaskell #-}
import Data.Lens
import Data.Lens.Template
import qualified Data.Map as Map
data Rec = Rec {
_field1 :: Int,
_field2 :: Int
} deriving(Show)
$( makeLens ''Rec )
recMap = Map.fromList [ ("field1", field1)
, ("field2", field2)
]
changeField :: Rec -> String -> Int -> Rec
changeField rec fieldName value = set rec
where set = (recMap Map.! fieldName) ^= value
main = do
let r = Rec { _field1 = 1, _field2 = 2 }
print r
let r' = changeField r "field1" 10
let r'' = changeField r' "field2" 20
print r''
或没有镜片:
import qualified Data.Map as Map
data Rec = Rec {
field1 :: Int,
field2 :: Int
} deriving(Show)
recMap = Map.fromList [ ("field1", \r v -> r { field1 = v })
, ("field2", \r v -> r { field2 = v })
]
changeField :: Rec -> String -> Int -> Rec
changeField rec fieldName value =
(recMap Map.! fieldName) rec value
main = do
let r = Rec { field1 = 1, field2 = 2 }
print r
let r' = changeField r "field1" 10
let r'' = changeField r' "field2" 20
print r''