【问题标题】:Haskell State as function typeHaskell State 作为函数类型
【发布时间】:2018-03-08 03:19:03
【问题描述】:

我很难理解本教程:https://acm.wustl.edu/functional/state-monad.php

我正在创建我自己的函数,它反转一个列表并返回一个State,其中包含最低元素和列表的反向。我对 Haskell 也很陌生。这是我的代码:

myFunct :: Ord a => [a] -> State a [a]
myFunct t = do
        let s = reverse t
        let a = minimum t
        return s a

我也找不到关于此的其他材料。这是我遇到的错误。

 Couldn't match type ‘[a]’
                 with ‘StateT a Data.Functor.Identity.Identity [a]’
  Expected type: a -> State a [a]
    Actual type: a -> [a]
• The function ‘return’ is applied to two arguments,
  its type is ‘a0 -> m0 a0’,
  it is specialized to ‘[a] -> a -> [a]’
  In a stmt of a 'do' block: return s a
  In the expression:
    do let s = reverse t
       let a = minimum t
       return s a

【问题讨论】:

  • 您有实际问题吗?
  • 你的问题是什么?
  • return 接受一个参数。
  • @WisnuAdiNurcahyo 这些不起作用。 sa 都不是函数。 OP 可能正在寻找 set 函数等。
  • @loneWolf 我上面的评论中有一个错字:我的意思是put,而不是setput 是在使用 State monad 时更改状态的直接方法。例如,参见State Monad and 'put' function in Haskell

标签: haskell state-monad


【解决方案1】:

你很幸运:State 是最容易理解的单子。

请不要因为您的函数根本不需要State 而气馁,只要您使用标准库中的reverseminimum

myFunct' :: Ord a => [a] -> ([a], a)
myFunct' xs = (reverse xs, minimum xs)

(它会像这样运行:)

λ myFunct' [1,2,3]
([3,2,1],1)

但请注意,为了让您将reverseminimum 应用到一个列表,您需要遍历它两次。这是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. 1Nothing 中较小的是1。因此,框中的Nothing 将替换为Just 1
  2. State 将再次被调用,就好像我们用这样的代码调用它:

    runState (reverseAndMinimum' [2,3] [1]) (Just 1)
    

以此类推,直到参数变为空列表,此时框肯定会包含最小的数字。

这个版本实际上比myFunct' 22%,而且使用的内存也少了一些。 (不过,您可能会查看编辑历史记录,但要花些力气才能找到它。)

就是这样。希望对你有帮助!

特别感谢 helped mereverseAndMinimum 设计的代码实际上优于 myFunct' 的 Li-Yao Xia。

【讨论】:

  • 感谢您花时间写这篇文章。我很感激。它确实有帮助。
【解决方案2】:

由于您使用的是do 块,我假设您想使用State,就像Monad 一样。这很好,但我建议将值列表 ([a]) 设为状态,将单个最小值设为“返回值”。

这意味着您可以将函数的类型简化为myFunct :: Ord a =&gt; State [a] a[a]是状态的类型,a是返回值的类型。

请注意,没有明确的“输入值”。在State monad 中,状态是始终存在的隐式上下文。

您现在可以像这样重写计算:

myFunct :: Ord a => State [a] a
myFunct = do
  t <- get
  let s = reverse t
  put s
  let a = minimum t
  return a

您可以更简洁地编写计算,但我选择明确地写出来以使发生的事情更清楚。 get 检索隐式状态的当前值,put 覆盖该状态。详情请见the documentation

你可以这样运行它:

*Q49164810> runState myFunct [42, 1337]
(42,[1337,42])
*Q49164810> runState myFunct [42, 1337, 0]
(0,[0,1337,42])
*Q49164810> evalState myFunct [42, 1337, 0]
0
*Q49164810> execState myFunct [42, 1337, 0]
[0,1337,42]

runState 采用初始状态,运行myFunct 计算,并返回返回值和最终状态。 evalState 工作方式相同,但只返回返回值,而exacState 只返回最终状态。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-31
    • 1970-01-01
    • 2022-10-01
    • 2018-01-14
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多