哈斯克尔中的一般函数变换案例



我正在(试图)学习 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)转换为move应用于停放的汽车并将其替换为新函数的函数Parking -> Int -> ()

合并接受答案后的示例

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) }

或者,我们可以使用状态 Monad 来避免/actual/可变性。

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 将有助于更轻松地将函数应用于状态 Monad "get" 让我们了解当前状态(Car 0),并将新版本放入状态。我们首先得到它,在上面应用 f,然后把它放在新的状态。

doStuff :: MonadState a m => (a -> a) -> m ()
doStuff f = get >>= put . f

这是 State 函数,这里我们只需用移动 1 调用 doStuff,它将修改我们的汽车(汽车 0)以移动 1 Int,因此新结果将是汽车 1。 然后我们说移动 3,它将变为汽车 4

myState :: State Car ()
myState = do
    doStuff $ move 1
    doStuff $ move 3

有了这个,我们可以运行启动函数并接收修改后的初始(Car 0)。

是的,你可以。

例如:

doCarStuff :: Parking -> (Car -> Car) -> IO ()
doCarStuff = modifyIORef

如果您重新排列move函数,以便Car参数排在最后,那么您可以这样做

doCarStuff myParking (move 5)

哪个做你想要的。

还有一种可能性,使用镜头库自动为您的对象派生"getters"和"setters"。首先有一点样板

{-# 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

最新更新