简体   繁体   English

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

[英]A case of general function transformation in Haskell

I'm (trying to) learning about mutable state in Haskell. 我(试图)在Haskell中学习可变状态。 To keep things simple, I define 为了简单起见,我定义了

data Car = Car { position :: Int }

and

move :: Car -> Int -> Car
move c n =  Car { position = n + (position c) }

Thus, move is a "pure" function that represents a transition of a Car into another one. 因此, move是一种“纯粹”的功能,代表了汽车向另一个汽车的过渡。

I think that I'll need to hold a Car in a mutable variable so I can have the current position (after some movements are made to the car). 我认为我需要将一辆Car放在一个可变的变量中,这样我才能拥有当前位置(在对汽车进行一些动作之后)。 Therefore I define (I hope this is the way to go, otherwise correct me) the following 因此,我定义(我希望这是方法,否则纠正我)以下

type Parking = IORef Car -- holds a car

newParking :: Car -> IO Parking
newParking = newIORef 

as well as trivial getCar :: Parking -> IO Car and setCar :: Parking -> Car -> IO () functions. 以及琐碎的getCar :: Parking -> IO CarsetCar :: Parking -> Car -> IO ()函数。

The above code seems to be fine. 上面的代码似乎没问题。

The question: 问题:

Could I define a function that transform any pure function like move :: Car -> Int -> Car into a function Parking -> Int -> () that applies move to the parked Car and replaces it with the new one? 我可以定义一个函数,将任何纯函数(如move :: Car -> Int -> Car转换为函数Parking -> Int -> () ,它将move到停放的Car并用新的替换它吗?

The example after incorporating the accepted answer 结合接受的答案后的例子

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

Alternatively, we could use the State Monad to avoid /actual/ mutability. 或者,我们可以使用State Monad来避免/实际/可变性。

import Control.Monad.State

Define the Car stucture 定义汽车结构

data Car = Car { position :: Int } deriving Show

initiate the state, we use execState, give it a function that will hold the state and the initial state (Car 0). 启动状态,我们使用execState,给它一个保持状态和初始状态的函数(Car 0)。

start :: Car
start = execState myState (Car 0)

move will move your car 移动会移动你的车

move :: Int -> Car -> Car
move n c = c { position = n + position c }

doStuff will help up make it easier to apply functions to the State Monad 'get' gets us the current state (Car 0) and 'put' puts the a new version into the state. doStuff将帮助更容易将函数应用于State Monad'get'让我们获得当前状态(Car 0)和'put'将新版本置于状态。 We first get it, apply f on it, and then put it in the new state. 我们首先得到它,对它应用f,然后将其置于新状态。

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

This is the State function, here we simply call doStuff with move 1 and it'll modify our car (Car 0) to move 1 Int, so the new result will be Car 1. then we say move 3 and it'll change to Car 4 这是状态函数,这里我们简单地用移动1调用doStuff并且它将修改我们的汽车(Car 0)以移动1 Int,因此新结果将是Car 1.然后我们说移动3并且它将改为车4

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

With this we can run the start function and receive our modified initial (Car 0). 有了这个,我们可以运行start函数并接收我们修改后的初始值(Car 0)。

Yes, you can. 是的你可以。

For example: 例如:

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

If you rearrange your move function so that the Car argument comes last, then you can do 如果您重新排列move函数以使Car参数最后,那么您可以这样做

doCarStuff myParking (move 5)

which does what you want. 哪能做到你想要的。

Here's one more possibility, using the lens library to automatically derive "getters" and "setters" for your objects. 这是另一种可能性,使用镜头库自动为您的对象派生“getters”和“setter”。 First there's a little bit of boilerplate 首先是一些样板

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens
import Control.Monad.State

newtype Car = Car { _position :: Int } deriving (Show)

makeLenses ''Car

Now you can write routines that look and feel 'imperative', for example 现在,您可以编写外观和感觉“命令式”的例程

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)

which results in 结果

>> main
Initial position is: 0
  Final position is: 3

If you want to use a generic function to modify the fields of a record, you can use over , as in 如果要使用通用函数来修改记录的字段,可以使用over ,如

>> let square x = x * x
>> over position square (Car 4)
Car {_position = 16}

and if you want that to have the same "imperative" feel you had before 如果你想拥有与之前相同的“命令”感觉

action = do
  position %= square
  p <- use position
  liftIO . putStrLn $ "New position: " ++ show p

and

>> evalStateT action (Car 4)
New position: 16

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM