我有一个在父函数中实例化的结构,我想通过调用该父函数中的函数来修改该实例化数据。这是一个人为的例子:
import Data.List
data MyLists = MyLists {
myInts :: [Int],
myBools :: [Bool]
} deriving (Show)
addIntToList :: Int -> MyLists -> MyLists
addIntToList x main_lists =
main_lists { myInts = Data.List.insert x my_ints }
-- might make a call to another child function that modifies main_list here, and so on (i.e., this is the vertical problem I see with this structuring)
where
my_ints = myInts main_lists
main :: IO()
main = do
let my_main_lists = MyLists [1,2,3] [False, True, False]
let my_new_main_lists = addIntToList 4 my_main_lists
print my_new_main_lists
let my_new_new_main_lists = addBoolToList True my_new_main_lists
print my_new_new_main_lists
-- and so on (this is the lateral problem I see with this code structuring)
构建此代码或完成与此类似的任务有哪些替代方法?有更简洁的方法吗?
我应该补充说,一旦你对子函数进行了长链函数调用,这会变得特别臭(即代码气味);他们最终都需要返回一个新的MyLists
或只是返回main_list
而没有做任何事情。那,父母可能还必须处理MyList
和另一个返回值(例如-> (Bool, MyList)
)。
因此,您可以想象函数调用的树结构都需要MyList参数并返回值;这似乎不是最佳的。
这是我正在谈论的那种事情的更具体的例子。浏览https://github.com/mokehehe/monao处的代码(haskell中的超级马里奥克隆)。您将看到state.monad从未被使用,并且有必须在整个代码中流动的上层结构(例如,Main.h中的GameGame)。
答案 0 :(得分:3)
您可以使用RecordWildCards扩展程序使其更简洁:
{-# LANGUAGE RecordWildCards #-}
import Data.List (insert)
addIntToList :: Int -> MyLists -> MyLists
addIntToList x ml@MyLists{..} = ml{ myInts = insert x myInts }
MyLists{..}
模式将MyLists
记录的属性转储到范围中。因此,我们可以在初始化新myInts
时轻松引用旧版myInts
。
相反,如果在表达式上下文中使用..
,它将使用范围中的相应名称填充未初始化的属性。 addIntToList
函数也可以写成:
addIntToList x MyLists{..} = MyLists{ myInts = insert x myInts, .. }
使用记录通配符可以做的一件很酷的事情:
someAction = do
myInts <- getInts :: IO [Int]
myBools <- getBools :: IO [Bool]
return MyLists{..}
此外,Data.List.insert
有点冗长。你可以说insert
,因为这个导入:
import Data.List
会将Data.List
中的所有名称导入名称空间。如果你不喜欢这个(例如因为你想在你自己的模块中定义一个名为insert
的函数),你可以导入它合格:
import qualified Data.List as List
… List.insert …
至于在程序中使用MyLists
,StateT
monad变换器非常有帮助:
{-# LANGUAGE RecordWildCards #-}
import Control.Monad.State
...
printList :: StateT MyLists IO ()
printList = liftIO . print =<< get
program :: StateT MyLists IO ()
program = do
printList
modify $ addIntToList 4
printList
modify $ addBoolToList True
printList
main :: IO()
main = evalStateT program $ MyLists [1,2,3] [False, True, False]
答案 1 :(得分:2)
您应该使用Data.Lens
:
MonadState
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.State
import Data.Lens
import Data.Lens.Template
import qualified Data.List
data MyLists = MyLists { _myInts :: [Int]
, _myBools :: [Bool]
} deriving ( Show )
$(makeLens ''MyLists)
addIntToList :: MonadState MyLists m => Int -> m [Int]
addIntToList i = myInts %= Data.List.insert i
addBoolToList :: MonadState MyLists m => Bool -> m [Bool]
addBoolToList b = myBools %= Data.List.insert b
program :: MonadState MyLists m => m ()
program = do
addIntToList 1
addBoolToList False
addIntToList 2
addBoolToList True
return ()
main = do
let ml = execState program MyLists { _myInts = []
, _myBools = []
}
print ml
修改强>
这就是我输入未经测试的代码所得到的。我改变了这个例子,所以它确实有效!您需要安装data-lens
,data-lens-fd
和data-lens-template
模块(使用cabal install
)。
答案 2 :(得分:1)
我不是专业人士,但我会尝试实现这个目标
import Data.List (insert)
data MyLists = MyLists { myInts :: [Int],
myBools :: [Bool]
} deriving (Show)
addIntToList :: Int -> MyLists -> MyLists
addIntToList i (MyLists is bs) = MyLists (insert i is) bs
-- if you don't care about order just do (i:is), which is faster
-- usually lists of i are denoted is (or sometimes I use ii) as a kind of plural
main :: IO()
main = do
let myMainList = MyLists [1,2,3] [False, True, False]
-- you would rather use CamelCase in Haskell than _
print $ addIntToList 4 myMainList