我决定尝试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个不同版本的操作?
答案 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
这将产生与以前相同的结果。这通常更有效(每个步骤打开的层数更少)并且可以更清洁。