我应该如何组织正常和#34;州"州monad中的函数版本?

时间:2014-11-18 21:30:30

标签: haskell

我决定尝试State Monad尝试清理我开始的一些项目。我遇到了命名/分区问题。

如果我有以下物品:

data Obj = Player { oPos :: Point }

data World = World { wKeys :: [Key], wPlayer :: Obj }

我可能有一个方便的功能,如:

setPlayer :: Obj -> World -> World
setPlayer o w = w{wPlayer = o}

和匹配的状态操作如:

setPlayerW :: Obj -> WorldState ()
setPlayerW o = get >>= put . setPlayer o

使用其他便利功能;为方便起见。

这类似的典型命名约定是什么?我用W写了国家版本,但那很难看。

"州版本"典型地与"对象版本隔离#34;在一个单独的文件中?

我完全错了吗?是否有更好的设置,然后我可能需要2个不同版本的操作?

2 个答案:

答案 0 :(得分:9)

就个人而言,我自己也不会为州版本提供单独的功能。相反,请改用modify函数:

do let o = player
   modify (setPlayer o)
   something else

从某种意义上说,您要查找的命名约定与使用modify的命名约定相同,只是折叠到每个函数的名称中。当我发现自己命名这样的函数时,我通常会尝试找到某种方法来组织 语言而不是使用它们的名称。有时,像这里一样,现有的功能就是你所需要的;其他时候,它涉及创建自己的功能以实现相同目的或将事物提取到模块中。

核心思想是,使用一流的语言结构来更新代码中的模式,而不是间接地将它们编码到名称中,这样做会更好。 (当然,如果这最终真的很尴尬,你就不应该这样做,但这里很好。)

答案 1 :(得分:3)

这是介绍lens库的好时机。起初这是一个令人生畏的图书馆,我仍然在努力解决其中一些更复杂的功能(这个版本的兔子洞非常深),但它可以真正简化你的State代码。要使用它,建议先更改数据类型,以便可以使用模板haskell:

import Control.Monad
import Control.Monad.State
import Control.Lens

-- Made assumption on what Point would look like
data Point = Point { _x :: Int, _y :: Int } deriving (Eq, Show)
data Obj = Player { _oPos :: Point } deriving (Eq, Show)
data World = World { _wKeys :: [Key], _wPlayer :: Obj } deriving (Eq, Show)

makeLenses ''Point
makeLenses ''Obj
makeLenses ''World

-- Also assumed this type
type WorldState = StateT World IO

然后你可以使用生成的镜头编写看起来非常重要的代码

setPlayerW :: Obj -> WorldState ()
setPlayerW o = wPlayer .= o

或者如果你想要非monadic版本

setPlayer :: Obj -> World -> World
setPlayer o = wPlayer .~ o

因此,这会使用.=运算符将wPlayer状态的World字段设置为新值。更令人印象深刻的是,您可以使用它来编写像

这样的代码
moveUp, moveDown, moveLeft, moveRight :: WorldState ()
moveUp    = wPlayer.oPos.y += 1
moveDown  = wPlayer.oPos.y -= 1
moveLeft  = wPlayer.oPos.x -= 1
moveRight = wPlayer.oPos.x += 1

这使得它看起来很像使用正常功能组合的面向对象语言。快速测试:

game :: WorldState ()
game = do
    replicateM_ 3 moveUp
    replicateM_ 5 moveLeft
    replicateM_ 10 moveRight
    replicateM_ 6 moveDown

> execStateT game $ World [] $ Player $ Point 0 0
World {_wKeys = [], _wPlayer = Player {_oPos = Point {_x = 5, _y = -3}}}

镜头库中有许多非常有趣且有用的运算符,并且内置了大量支持,可以将它与StateT堆栈一起使用。

另一个不错的功能是zoom功能,它可以拍摄镜头并“放大”它,让你操作就像你的状态具有你所放大的任何值一样。一个例子是

game = zoom (wPlayer.oPos) $ do
    y += 3
    x -= 5
    x += 10
    y -= 6

这将产生与以前相同的结果。这通常更有效(每个步骤打开的层数更少)并且可以更清洁。