2

我有一个在父函数中实例化的结构,我想通过从该父函数调用函数来修改该实例化数据。这是一个人为的例子:

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.hs 中的 GameGame)。

4

3 回答 3

3

您可以使用RecordWildCards扩展使其更简洁:

{-# LANGUAGE RecordWildCards #-}
import Data.List (insert)

addIntToList :: Int -> MyLists -> MyLists
addIntToList x ml@MyLists{..} = ml{ myInts = insert x myInts }

MyLists{..}模式将记录的属性转储MyLists到范围内。myInts因此,我们可以在初始化 new 时轻松引用 old 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

会将所有名称 fromData.List导入命名空间。如果你不喜欢这样(例如,因为你想在你自己的模块中定义一个名为 的函数insert),你可以导入它限定:

import qualified Data.List as List

… List.insert …

MyLists在程序中使用而言,StateTmonad 转换器非常有帮助:

{-# 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]
于 2012-05-15T18:20:05.560 回答
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-lensdata-lens-fddata-lens-template模块(使用cabal install)。

于 2012-05-15T19:19:04.530 回答
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
于 2012-05-15T18:15:06.450 回答