你很幸运:State 是最容易理解的单子。
请不要因为您的函数根本不需要State 而气馁,只要您使用标准库中的reverse 和minimum。
myFunct' :: Ord a => [a] -> ([a], a)
myFunct' xs = (reverse xs, minimum xs)
(它会像这样运行:)
λ myFunct' [1,2,3]
([3,2,1],1)
但请注意,为了让您将reverse 和minimum 应用到一个列表,您需要遍历它两次。这是State 可能派上用场的时候:使用它,您只能遍历列表一次,因此,希望获得一些加速。继续阅读以了解如何操作。
所以,State 是一种特殊的函数:你给它的东西(也称为“状态”)被保存在一个魔法盒子里,你可以在其中观察或替换它随时与另一件相同类型的东西。如果您有使用命令式语言的经验,您可能会很容易将State 视为命令式过程,而将“状态”视为局部变量。让我们回顾一下您可以用来构建和执行State 的工具:
-
您可以使用(名称不当)函数get观察框中的内容。请注意,这不会以任何方式更改状态 - 您获得的只是其当前值的不可变副本;东西留在盒子里。
您通常会将观察结果与名称相关联,然后将其用作普通值 - 例如,传递给纯函数:
stateExample1 :: State Integer Integer
stateExample1 = do
x <- get -- This is where we observe state and associate it with the name "x".
return $ x * 2 -- (* 2) is an example of a pure function.
λ runState stateExample1 10
(20,10) -- The first is the return value, the second is the (unchanged) state.
-
您可以将框中的内容替换为另一个适当键入的内容;使用函数put:
stateExample2 :: State Integer Integer
stateExample2 = do
x <- get
put $ x * 2 -- You may think of it as though it were "x = x * 2"
-- in an imperative language.
return x
λ runState stateExample2 10
(10,20) -- Now we have changed the state, and return its initial value for reference.
请注意,虽然我们改变了状态,但我们对它的观察(我们命名为“x”)仍然具有相同的值。
-
你可以运行State函数,给它一个参数(我们称之为“初始状态”):
y = runState stateExample1 10
同理:
y = stateExample1(10);
- 使用类似 C 语法的命令式语言,除了您同时获得返回值和 最终状态。
有了这些知识,我们现在可以像这样重写您提议的myFunct:
myFunct :: Ord a => [a] -> State (Maybe a) [a]
myFunct [ ] = return [ ]
myFunct t = do
let s = reverse t
let a = minimum t
put (Just a)
return s
λ runState (myFunct [1,2,3]) (Just (-100))
([3,2,1],Just 1)
λ runState (myFunct []) (Just (-100))
([],Just (-100))
如果我们将State 视为一个命令式过程,那么反向列表就是它返回的内容,而列表的最小值就是它的最终状态。由于列表可能为空,我们为最小值提供了一个可选的默认值。这使得函数 total 被认为是很好的 Haskell 风格:
λ myFunct' []
([],*** Exception: Prelude.minimum: empty list
λ runState (myFunct []) Nothing
([],Nothing)
现在,让我们通过编写一个一次性返回列表的最小值和倒数的函数来获得State 的好处:
reverseAndMinimum :: Ord a => [a] -> ([a], Maybe a)
reverseAndMinimum xs = runState (reverseAndMinimum' xs [ ]) Nothing
reverseAndMinimum' :: Ord a => [a] -> [a] -> State (Maybe a) [a]
reverseAndMinimum' [ ] res = return res
reverseAndMinimum' (x:xs) res = do
smallestSoFar <- get
case smallestSoFar of
Nothing -> put $ Just x
Just y -> when (x < y) (put $ Just x)
reverseAndMinimum' xs (x: res)
首先,这是一种迭代算法,因此需要最小值的起始值。我们将这一事实隐藏在 reverseAndMinimum' 中,并提供 Nothing 作为起始值。
我从现代Prelude.reverse借来的反向部分的逻辑。我们只需将元素从第一个参数xs 移动到第二个参数res,直到xs 为空。
-
这是查找当前x 和存储在状态框中的值中较小的部分。我希望你会觉得它可读。
case smallestSoFar of
Nothing -> put $ Just x
Just y -> when (x < y) (put $ Just x)
-
这是执行递归的部分:
reverseAndMinimum' xs (x: res)
它再次适用reverseAndMinimum',但适用于更小的列表xs; monadic 布线会自动将当前最小值的盒子向下传输。
让我们跟踪对reverseAndMinimum' 的调用的执行情况。假设我们说:
runState (reverseAndMinimum' [1,2,3] [ ]) Nothing
会发生什么?
-
1 和Nothing 中较小的是1。因此,框中的Nothing 将替换为Just 1。
-
State 将再次被调用,就好像我们用这样的代码调用它:
runState (reverseAndMinimum' [2,3] [1]) (Just 1)
以此类推,直到参数变为空列表,此时框肯定会包含最小的数字。
这个版本实际上比myFunct'快快 22%,而且使用的内存也少了一些。 (不过,您可能会查看编辑历史记录,但要花些力气才能找到它。)
就是这样。希望对你有帮助!
特别感谢 helped me 为 reverseAndMinimum 设计的代码实际上优于 myFunct' 的 Li-Yao Xia。