我(试图)在Haskell中学习可变状态。为了简单起见,我定义了
data Car = Car { position :: Int }
和
move :: Car -> Int -> Car
move c n = Car { position = n + (position c) }
因此,move
是一个“纯粹”的函数,代表汽车转换为另一个汽车。
我认为我需要将一辆Car放在一个可变变量中,这样我才能拥有当前位置(在对汽车进行一些动作之后)。 因此我定义(我希望这是方法,否则纠正我)以下
type Parking = IORef Car -- holds a car
newParking :: Car -> IO Parking
newParking = newIORef
以及琐碎的getCar :: Parking -> IO Car
和setCar :: Parking -> Car -> IO ()
函数。
上面的代码似乎没问题。
问题:
我可以定义一个函数,将任何纯函数(如move :: Car -> Int -> Car
)转换为函数Parking -> Int -> ()
,将move
应用于停放的汽车,并将其替换为新的一个?
纳入接受的答案后的示例
import Data.IORef
import Control.Concurrent
-- -----------------------------------------------------
timeGoesBy place = do
moveTheCar place
threadDelay 1000000
timeGoesBy place
moveTheCar place = do
car <- getCar place
print $ getPos car
modifyCar place (move 7)
-- -----------------------------------------------------
main = do
place <- newParking (newCar 1000)
timeGoesBy place
print "end"
-- -----------------------------------------------------
type Parking = IORef Car -- mutable var for holding a car (the car may be replaced)
newParking :: Car -> IO Parking
newParking = newIORef
getCar :: Parking -> IO Car
getCar = readIORef
setCar :: Parking -> Car -> IO ()
setCar = writeIORef
modifyCar :: Parking -> (Car -> Car) -> IO ()
modifyCar = modifyIORef
-- -----------------------------------------------------
data Car = Car { position :: Int } -- Car data type ("pure")
-- create a car
newCar :: Int -> Car
newCar v = Car { position = v}
-- get the position of a car
getPos :: Car -> Int
getPos c = (position c)
-- move : transform a given car into a new "moved car"
move :: Int -> Car -> Car -- first the int so that we can curry (i.e. move 7)
move n car = Car { position = n + (position car) }
答案 0 :(得分:11)
或者,我们可以使用State Monad来避免/实际/可变性。
import Control.Monad.State
定义汽车结构
data Car = Car { position :: Int } deriving Show
启动状态,我们使用execState,给它一个保持状态和初始状态的函数(Car 0)。
start :: Car
start = execState myState (Car 0)
移动会移动你的车
move :: Int -> Car -> Car
move n c = c { position = n + position c }
doStuff将帮助更容易将函数应用于State Monad'get'让我们获得当前状态(Car 0)并且'put'将新版本置于状态。我们首先得到它,对它应用f,然后将其置于新状态。
doStuff :: MonadState a m => (a -> a) -> m ()
doStuff f = get >>= put . f
这是State函数,这里我们简单地用move 1调用doStuff并且它将修改我们的汽车(Car 0)以移动1 Int,所以新结果将是Car 1.然后我们说移动3并且它'将改为Car 4
myState :: State Car ()
myState = do
doStuff $ move 1
doStuff $ move 3
有了这个,我们可以运行start函数并接收修改后的初始值(Car 0)。
答案 1 :(得分:8)
是的,你可以。
例如:
doCarStuff :: Parking -> (Car -> Car) -> IO ()
doCarStuff = modifyIORef
如果您重新排列move
函数以使Car
参数最后出现,则可以执行
doCarStuff myParking (move 5)
做你想做的事。
答案 2 :(得分:3)
这是另一种可能性,使用镜头库自动为您的对象派生“getters”和“setter”。首先是一些样板文件
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Monad.State
newtype Car = Car { _position :: Int } deriving (Show)
makeLenses ''Car
现在,您可以编写外观和感觉“命令性”的例程,例如
program = do
p <- use position
liftIO . putStrLn $ "Initial position is: " ++ show p
position += 3
q <- use position
liftIO . putStrLn $ " Final position is: " ++ show q
main = evalStateT program (Car 0)
导致
>> main
Initial position is: 0
Final position is: 3
如果您想使用通用功能修改记录的字段,可以使用over
,如
>> let square x = x * x
>> over position square (Car 4)
Car {_position = 16}
如果你希望它具有与之前相同的“命令”感觉
action = do
position %= square
p <- use position
liftIO . putStrLn $ "New position: " ++ show p
和
>> evalStateT action (Car 4)
New position: 16