Haskell状态为函数类型

时间:2018-03-08 03:19:04

标签: haskell state-monad

我很难理解本教程: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

2 个答案:

答案 0 :(得分:2)

由于您使用了do阻止,我认为您希望将State用作Monad。那没关系,但我建议,将值列表([a])设为州,将单个最小值列为“返回值”。

这意味着您可以将函数类型简化为myFunct :: Ord a => 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只返回最终状态。

答案 1 :(得分:2)

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

在您使用标准库中的Statereverse的情况下,您的功能根本不需要minimum,请不要气馁。

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)
    
  3. 依此类推,直到参数变为空列表,此时该框肯定会包含最小的数字。

    此版本实际上更快 } 22%,并且使用的内存也更少。 (虽然,你可以查看编辑历史记录,但需要付出一些努力才能实现。)

    就是这样。我希望它有所帮助!

    特别感谢Li-Yao Xia helped me设计了myFunct'实际击败reverseAndMinimum的代码。