Haskell中一般函数变换的一个案例

时间:2014-01-16 12:39:30

标签: haskell

我(试图)在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 CarsetCar :: 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) }

3 个答案:

答案 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