Haskell根据字段名字符串动态设置记录字段?
假设我有以下记录:Haskell根据字段名字符串动态设置记录字段?,haskell,record,Haskell,Record,假设我有以下记录: data Rec = Rec { field1 :: Int, field2 :: Int } 如何编写函数: changeField :: Rec -> String -> Int -> Rec changeField rec fieldName value 这样我就可以将字符串“field1”或“field2”传递到fieldName参数中,并让它更新相关字段?我理解数据。这里使用的是Data和数据。Typeable,但我无法理解这两个
data Rec = Rec {
field1 :: Int,
field2 :: Int
}
如何编写函数:
changeField :: Rec -> String -> Int -> Rec
changeField rec fieldName value
这样我就可以将字符串“field1”或“field2”传递到fieldName
参数中,并让它更新相关字段?我理解数据。这里使用的是Data
和数据。Typeable
,但我无法理解这两个包
我见过一个这样做的库示例是cmdArgs。以下是一篇关于如何使用此库的博客文章:
{-# 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
您可以从字段名称到其镜头构建贴图:
{-# 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''
好的,这里有一个解决方案,它不使用模板haskell,也不需要您手动管理字段映射 我实现了一个更通用的
modifyField
,它接受一个mutator函数,并实现了setField
(neechangeField
)使用它和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)
你可能不想这么做。你听说了吗?我认为实现这一点的唯一方法是将字段名与其参数索引配对,并使用
gmapQi
或类似工具。(您需要将派生(可键入,数据)
添加到您的记录声明中,这样才有希望工作;它不能用于任意类型。)我确实想这样做。我想创建一个库,用户可以在其中提供记录,库可以通过解析一些文本来填充记录。文本将包含对我要设置的记录中字段的引用;cmdargs的隐式模式是最受诟病的Haskell库之一,因为它的杂质:)不过,如果您确实想实现这一点,那么我仍然建议使用模板Haskell生成recMap
;它更灵活,也不那么神奇。这种做法违背了Haskell的编译时安全思想。你应该考虑使用更少的动态。solution@Ana:当然可以,但对这样一个问题的完整回答可能需要指出,可能有更好的方法来实现同样的目标;如果有人问“我如何使用unsafeccerce
在Haskell中的整数类型之间进行转换?”,如果不指出您应该使用fromIntegral
,那将是失职的;因此,我的评论。recMap正是我正在避免的项目。我需要专门处理每个字段,并且我希望动态地完成从字符串到字段的映射。
{-# 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)