将新记录添加到同名的地图中

时间:2016-10-18 20:31:43

标签: haskell

我正在尝试使用Data.Map编写一个小程序来存储员工ID和名称,您可以插入,删除,查找等。

import qualified Data.Map as M
main = do
  let emptymap = M.empty
      map1 = M.insert 10001 "John" emptymap
  print (M.lookup 10001 map1)

从这里开始,如果我将另一名员工添加到map1,我是否需要为其命名?如果我添加了以下行,则不会编译。

      map1 = M.insert 10002 "Paul" map1

但是,如果我添加以下行,它可以工作(虽然它提供了很多警告)

      map2 = M.insert 10002 "Paul" map1

我可以使用fromList()创建一个包含两名员工的新地图,但每次添加新员工(或删除员工)时,我都必须为结果指定一个新名称?我想要一个具有相同名称的最新信息的地图。请帮忙。

2 个答案:

答案 0 :(得分:2)

不一定;您可以通过合成将插入链接在一起:

main = do
  let emptymap = M.empty
      map = M.insert 10001 "John" . M.insert 10002 Paul $ emptymap
  print (M.lookup 10001 map)

如果您想稍后添加的其他名称,则需要一个新名称;否则,你定义一个“无限”的地图。

main = do
  let emptymap = M.empty
      map = M.insert 10001 "John" . M.insert 10002 Paul $ emptymap
  print (M.lookup 10001 map)
  let map' = M.insert 10003 "Kate" map
  print (M.lookup 10003 map)

但是,如果您需要访问多个地方的相同地图,您可能希望在State monad中工作,该monad将负责管理您对地图的所有引用。

答案 1 :(得分:1)

Haskell中的变量是不可变的。如果您想要可变状态,可以使用可变引用类型,例如IORef(或STRef):

import Data.IORef (modifyIORef', newIORef, readIORef)
import qualified Data.Map as Map

type Employees = Map Id Name
type Id = Int
type Name = String

main :: IO ()
main = do
    employees <- newIORef (Map.empty :: Employees)
    addEmployee employees 10001 "John"
    addEmployee employees 10002 "Paul"
    print . Map.toList =<< readIORef employees

addEmployee :: IORef Employees -> Id -> Name -> IO ()
addEmployee employees id name
    = modifyIORef' employees (Map.insert id name)

然而,这不是非常可测试或可组合 - 您仍然坚持使用IO(或ST)。您可以使用State使其纯净:

import Control.Monad.Trans.State (evalState, modify, gets)

main :: IO ()
main = print $ flip evalState (Map.empty :: Employees) $ do
    modify $ addEmployee 10001 "John"
    modify $ addEmployee 10002 "Paul"
    gets Map.toList

addEmployee :: Id -> Name -> Employees -> Employees
addEmployee = Map.insert

然后,关于状态的所有修改和查询函数(例如addEmployee)都可以是纯粹的,您可以单独测试它们:

removeEmployee 10001 (addEmployee 10001 "John" Map.empty)  ==  Map.empty
findEmployee 10002 Map.empty  ==  Nothing

如果您需要将其他一些效果(例如I / O)与此状态交错,则可以使用StateT

import Control.Monad.Trans.Class (lift)

main :: IO ()
main = do
    result <- flip evalStateT (Map.empty :: Employees) $ do
        lift $ putStrLn "modifying"
        modify $ addEmployee 10001 "John"
        lift $ putStrLn "modifying again"
        modify $ addEmployee 10002 "Paul"
        lift $ putStrLn "returning"
        gets Map.toList
    print result