Haskell记录更新

时间:2015-11-02 03:40:10

标签: haskell record

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

我就是这样做的,有没有更优雅的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 个答案:

答案 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必须位于自己的模块中。