【问题标题】:Pattern matching against a tuple in the IO Monad in Haskell在 Haskell 的 IO Monad 中对元组进行模式匹配
【发布时间】:2009-12-28 19:07:18
【问题描述】:

我在业余时间一直在研究 Haskell,最近涉足一元函数领域。我已经将我一直在做的练习中的代码提取到这个非常人为的示例中,以找出我遇到的确切问题:

import System.Random

rndPermu :: [a] -> IO (a, [a])
rndPermu xs = (front, back)
    where (front, back) = hurf xs

hurf :: [a] -> IO (a, [a])
hurf xs = randomRIO (0, (length xs) - 1) >>= \r -> return $ removeAt r xs

removeAt :: Int -> [a] -> (a, [a])
removeAt n xs = (e, rest)
    where e    = xs !! n
          rest = take n xs ++ (tail $ drop n xs)

rndPermu 在加载到 GHCi 时会产生类型错误,说明在“where”子句中需要类型 (t, t1) 但收到了 IO (a, [a])。我可以使用 (liftM fst) 之类的东西从元组中提取单个项目并分配一个值,但这显然是一种草率而迂回的处理方式。我觉得我可能会因为一些语法的细微差别而绊倒在我脸上。如何解决这种类型的错误?应该可以直接匹配一个包裹在 monad 中的元组,不是吗?

【问题讨论】:

    标签: haskell pattern-matching tuples


    【解决方案1】:

    我不知道你为什么没有

    rndPermu xs = hurf xs
    

    但要回答你提出的问题,试试这个

    rndPermu xs = do (front, back) <- hurf xs
                     return (front, back)
    

    我的理解是你不能直接匹配IO中的东西。您必须先使用&lt;- 语法将其提取出来。

    【讨论】:

    • 就像我说的,这是人为的。毫无意义的“where”子句被添加了,因为这是我正在测试和好奇的。我天真地试图通过在我认为可以的地方避免看起来像命令式的“do”块,并通过做类型系统不打算适应的奇怪事情来跟进良好功能形式的陈规定型观念。但这显然是更明智的方式。你的建议是正确的,对我有用。谢谢。
    【解决方案2】:

    如果我正确理解您要执行的操作,rndPermu 会尝试获取 hurf 返回的 IO 中的值并从中删除 IO,例如 rndPermu :: IO a -&gt; a。这是不可能的。 IO monad 中的返回值表明 hurf 函数使用 IO,所有使用 hurf 调用结果的函数因此也将间接使用 IO:它们的返回值也应该在 IO单子。这是由类型系统强制执行的。

    如果你只想在 monad 中使用模式匹配,最直接的方法是使用 do-notation:

    rndPermu :: [a] -> IO (a, [a])
    rndPermu xs =
       do (front, back) <- hurf xs
          return (front, back)
    

    一种常见的模式是使用不同的纯函数对值进行进一步处理。 这些函数只是从 IO 或不同的 monad 调用的,但它们不需要知道这一点:

    -- pure function to do something with the result of |hurf|
    modify :: (a, [a]) -> (a, [a])
    modify (a, as) = (a, reverse as)
    
    rndPermu :: [a] -> IO (a, [a])
    rndPermu xs =
       do r <- hurf xs
          return (modify r)
    -- or, with >>= operator:
    -- hurf xs >>= return . modify
    

    【讨论】:

    • 不,不。我知道 IO monad 是单向的,我读过的文章和书籍很早就把它打入了我的脑海。我担心这有点模棱两可,所以我在发帖后回去将类型签名添加到 rndPermu 以澄清我的意思是让它保留在 IO 中,尽管我认为 GHCi 无论如何都会推断出这一点。我对从 IO 中提取受污染的值不感兴趣;我知道即使有可能,那也会很糟糕。具体来说,是否可以对我感到困惑的 IO monad 中的元组进行模式匹配。
    • > ...我在发帖后回过头来将类型签名添加到 rndPermu 以澄清我的意思是让它保留在 IO 中,尽管我认为 GHCi 无论如何都会推断出这一点。哦,但它确实推断出类型。它只是没有推断出你的意图是什么。此外,正如其他人已经阐述的那样,您可以与 IO monad(或任何 monad)中的元组完美匹配;您只是不能以暗示您将价值从其“一元之家”中分离出来的方式来做到这一点。
    【解决方案3】:

    作为do 块的替代方法,您可以在绑定一元值的函数中进行模式匹配:

    rndPermu xs = hurf xs >>= \(front, back) -> return (front, back)
    
    rndPermu xs = hurf xs >>= \res -> case res of (front, back) -> return (front, back)
    

    【讨论】:

      【解决方案4】:

      为了回答您评论中的问题,GHCi 确实推断 rndPermu 应该有一个 IO 类型。那不是问题。问题是这一行:

       where (front, back) = hurf xs
      

      类型推断只是(松散地)意味着您不必指定您正在处理的表达式的类型。类型推断并不意味着 Haskell 会简单地将一种类型的值默默地转换为另一种类型;事实上恰恰相反。正如其他人所提到的,如果您不想编写 do 块,则不必编写,但您必须处理具有 IO 值的事实。

      【讨论】:

      • 这可能有意义,也可能没有意义,但我最初的想法并不是我必须在这种强类型语言中强制类型,而是某种多态性会在模式匹配,因此 monad 中的元组通常只是元组的子集,并且在该语句中根本不会有任何冲突。否则,我试图找到一些语法上有效的等价物:“ where IO (front, back) = hurf xs ”,希望模式本身可以以某种方式转换为 IO monad。有点奇怪,我知道。
      • 但仅此而已。 monad 中的元组通常不是元组的子集。一般来说,它是 monad 的一个子集。但它不一定是 IO 单子的一个子集。您可以对任何 monad 使用类似的构造。 IO (front, back) = hurf xs 不起作用的原因是因为IO 不是一个简单的数据构造函数。可能还有其他数据类型可以使用,但不是 IO。
      • 只是为了增加重点。我想我现在明白了与其他答复的混淆是什么。当您编写 IO (front, back) = hurf xs 时,您可能并不打算“突破” IO monad,但对于 Haskell 程序员来说,这正是您正在尝试做的事情。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-15
      • 1970-01-01
      • 2022-01-19
      • 2016-09-04
      相关资源
      最近更新 更多