我想用外部(流)名称/值对更新记录。记录的字段有不同的类型。
我就是这样做的,有没有更优雅的Haskell方法呢?
data MyRec = MyRec {field1 :: String, field2 :: Int, field3 :: Bool} deriving Show
defRec = MyRec {field1 = "", field2 = 0, field3 = False}
singleUpdate :: String -> MyRec -> MyRec -> MyRec
singleUpdate "field1" urec rec = rec {field1 = field1 urec}
singleUpdate "field2" urec rec = rec {field2 = field2 urec}
singleUpdate "field3" urec rec = rec {field3 = field3 urec}
singleUpdate _ _ rec = rec
update :: [(String, MyRec)] -> MyRec -> MyRec
update flds rec = foldl (flip (uncurry singleUpdate)) rec flds
nameVals = [("field1",defRec {field1 = "foo"}), ("field3",defRec {field3 = True})]
updtdRec = update nameVals defRec
updtdRec返回MyRec {field1 = "foo", field2 = 0, field3 = True}
是否有更紧凑的方式,更接近这样的事情:update field val = MyRec {field = val}
(这只是伪代码),适用于所有字段,而不必为每个字段指定函数实现?
答案 0 :(得分:2)
正如Sibi评论,你想要的是镜头。它提供了一种操纵数据类型的强大方法。
示例:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data MyRec = MyRec { _field1 :: String, _field2 :: Int, _field3 :: Bool }
deriving(Show)
makeLenses ''MyRec
main :: IO ()
main = do
let myrec = MyRec { _field1 = "", _field2 = 0, _field3 = False }
myrec' = myrec & field1 .~ "updated"
myrec'' = myrec' & field2 .~ 1
myrec''' = myrec'' & field3 .~ True
print myrec -- MyRec {_field1 = "", _field2 = 0, _field3 = False}
print myrec' -- MyRec {_field1 = "updated", _field2 = 0, _field3 = False}
print myrec'' -- MyRec {_field1 = "updated", _field2 = 1, _field3 = False}
print myrec''' -- MyRec {_field1 = "updated", _field2 = 1, _field3 = True}
您可以编写类似update field val = MyRec {field = val}
的函数。
update :: MyRec -> ASetter MyRec MyRec t t -> t -> MyRec
update record field val = record & field .~ val
> update defRec field1 "updated"
MyRec {_field1 = "updated", _field2 = 0, _field3 = False}
这些非常紧凑,但是镜头的元组和相应的值具有不同的类型。例如,(field1, "updated")
和(field2, 1)
具有不同的类型,因此很难编写列表进行更新。
以下链接将帮助您开始使用镜头。
https://hackage.haskell.org/package/lens-tutorial-1.0.0/docs/Control-Lens-Tutorial.html
答案 1 :(得分:1)
每次更新只是MyRec -> MyRec
类型的函数。
您的代码变为:
data MyRec = MyRec {field1 :: String, field2 :: Int, field3 :: Bool} deriving Show
defRec = MyRec {field1 = "", field2 = 0, field3 = False}
update :: [MyRec -> MyRec] -> MyRec -> MyRec
update flds rec = foldl' (flip ($)) rec flds
nameVals = [(\ r -> r { field1 = "foo" }), (\ r -> r { field3 = True })]
updtdRec = update nameVals defRec
答案 2 :(得分:1)
您可以使用TemplateHaskell来完成。这是一种方法:
文件MyTH2.hs:
module MyTH2 where
import Language.Haskell.TH
import Data.List
mkUpdaterForRecordType :: String -> Name -> Q [Dec]
mkUpdaterForRecordType fname tyname = do
i <- reify tyname
let cs = case i of
TyConI (DataD _ _ _ cs _) -> cs
_ -> []
ls = [ l | (RecC _ ls) <- cs, l <- ls ]
ns = [ nameBase n | (n, _, _) <- ls ]
mkUpdater fname ns
mkUpdater :: String -> [String] -> Q [Dec]
mkUpdater fname names = do
let clauses = map mkClause names
varb = mkName "b"
clause0 = Clause [ WildP, WildP, (VarP varb) ] (NormalB $ VarE varb) []
decl1 = FunD (mkName fname) (clauses ++ [clause0])
return [decl1]
mkClause :: String -> Clause
mkClause fldname =
let vara = mkName "a"
varb = mkName "b"
varf = mkName fldname
pats = [ LitP (StringL fldname) , VarP vara, VarP varb ]
body = NormalB $ RecUpdE (VarE varb) [ (varf, AppE (VarE varf) (VarE vara)) ]
clause = Clause pats body []
in clause
文件Main.hs:
{-# LANGUAGE TemplateHaskell #-}
import MyTH2
data MyRec = MyRec {field1 :: String, field2 :: Int, field3 :: Bool} deriving Show
old1 = MyRec "old1" 1 False
new2 = MyRec "new2" 2 True
$(mkUpdaterForRecordType "update" ''MyRec)
test1 = update "field1" new2 old1
test2 = update "field2" new2 old1
test3 = update "field3" new2 old1
test4 = update "other" new2 old1 -- no update performed
请注意,模板Haskell必须位于自己的模块中。