Haskell记录更新

问题描述:

我想用外部(流)名称/值对更新记录。记录的字段有不同的类型。Haskell记录更新

这就是我做到的,有没有更优雅的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}(这仅仅是伪代码),它适用于所有领域,而不必指定函数实现为每个领域?

+3

您正在寻找镜头库。 – 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} 

这些都非常紧凑,但lense和相应值的元组有不同的类型。例如,(field1, "updated")(field2, 1)具有不同的类型,因此很难编写更新列表。

以下链接将帮助您开始使用镜头。

https://hackage.haskell.org/package/lens-tutorial-1.0.0/docs/Control-Lens-Tutorial.html

每次更新只是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 

你可以用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 

注意,模板哈斯克尔必须是自己的模块中。