简体   繁体   English

Haskell记录更新

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM