[英]Haskell records update
I would like to update a record with external (streams of) name/value pairs. 我想用外部(流)名称/值对更新记录。 The record's fields have different types.
记录的字段有不同的类型。
That's how I did it, is there a more elegant Haskell way to do this? 这就是我做到的,是否有更优雅的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 returns MyRec {field1 = "foo", field2 = 0, field3 = True}
updtdRec返回
MyRec {field1 = "foo", field2 = 0, field3 = True}
Is there a more compact way, closer to something like this : update field val = MyRec {field = val}
(this is just pseudo-code), which works for all fields, without having to specify the function implementation for each field? 是否有更紧凑的方式,更接近这样的事情:
update field val = MyRec {field = val}
(这只是伪代码),它适用于所有字段,而不必为每个字段指定函数实现?
As Sibi comments, what you want is lens. 正如Sibi评论,你想要的是镜头。 It provides a powerful way of manipulating data types.
它提供了一种操纵数据类型的强大方法。
Example: 例:
{-# 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}
You can write a function like update field val = MyRec {field = val}
. 您可以编写像
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}
These are very compact, but tuples of a lense and a corresponding value have different types. 这些非常紧凑,但是镜头的元组和相应的值具有不同的类型。 For example,
(field1, "updated")
and (field2, 1)
have different types, so it is difficult to write a list for updating. 例如,
(field1, "updated")
和(field2, 1)
具有不同的类型,因此很难编写用于更新的列表。
The link below will help you get started with lens. 以下链接将帮助您开始使用镜头。
https://hackage.haskell.org/package/lens-tutorial-1.0.0/docs/Control-Lens-Tutorial.html https://hackage.haskell.org/package/lens-tutorial-1.0.0/docs/Control-Lens-Tutorial.html
Every update is just a function of type MyRec -> MyRec
. 每次更新都只是
MyRec -> MyRec
类型的函数。
Your code becomes: 您的代码变为:
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
You can do it with TemplateHaskell. 你可以使用TemplateHaskell来完成它。 Here's one way to do it:
这是一种方法:
File MyTH2.hs: 文件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
File Main.hs: 文件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
Note that the Template Haskell has to be in its own module. 请注意,模板Haskell必须位于其自己的模块中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.