1

虽然我可以应用一个函数两次并将结果绑定到一个元组中:


  let foo :: Num a => a -> a
      foo x = x + 1

  let (x,y) = (foo 10, foo 20)

do这不能在一个块内完成(至少我不知道如何正确地做到这一点) :


  let bar :: Num a => a -> IO a
      bar x = do
                  let y = x + 1
                  return y

  let test :: Num a => IO a
      test = do
                 (x,y) <- (bar 10, bar 20)
                 return y

输入 GHCI REPL 时出现以下错误:


:29:15:
    Couldn't match expected type ‘IO a1’ with actual type ‘(t0, a)’
    Relevant bindings include
      test :: IO a (bound at :28:5)
    In the pattern: (x, y)
    In a stmt of a 'do' block: (x, y) <- (bar 10, bar 20)
    In the expression:
      do { (x, y) <- (bar 10, bar 20);
           return y }

:29:24:
    Couldn't match type ‘(,) (IO a0)’ with ‘IO’
    Expected type: IO (IO a1)
      Actual type: (IO a0, IO a1)
    In a stmt of a 'do' block: (x, y) <- (bar 10, bar 20)
    In the expression:
      do { (x, y) <- (bar 10, bar 20);
           return y }
    In an equation for ‘test’:
        test
          = do { (x, y) <- (bar 10, bar 20);
                 return y }

我显然可以用更详细的方法来解决它:


  let test' :: Num a => IO a
      test' = do
                 x <- bar 10
                 y <- bar 20
                 return y

有没有一种正确的表达方式test而不是让它喜欢test'

4

3 回答 3

7
import Control.Applicative

test = do (x,y) <- (,) <$> bar 10 <*> bar 20
          return y

阿卡(x,y) <- liftA2(,) (bar 10) (bar 20)

当然,对于这个特定的示例(x刚刚被丢弃的地方),只写

test = bar 20
于 2015-09-18T08:28:30.083 回答
6

我将自由地建议对您的代码进行一些更改。这是我的版本:

import Control.Monad

-- no need for the do and let
bar :: Num a => a -> IO a
bar x = return $ x + 1 -- or: bar = return . (1+)

-- liftM2 to make (,) work on IO values
test :: Num a => IO a
test = do (x,y) <- liftM2 (,) (bar 10) (bar 20) -- or: (,) <$> bar 10 <*> bar 20
          return y

-- show that this actually works
main :: IO ()
main = test >>= print

您的类型不匹配:您的(bar 10, bar 20)评估结果为 typeNum a => (IO a, IO a)但您将其视为Num a => IO (a, a). 通过提升(,),我们使它在IO值上起作用,并返回一个IO值。

看看这个(GHCi,import Control.Monad得到liftM2):

:t (,)
-- type is :: a -> b -> (a, b)

:t liftM2 (,)
-- type is :: Monad m => m a -> m b -> m (a, b)

在我们的例子中MonadIO单子。因此, 的最终输出将在do-blockliftM2 (,)内很好地工作,因为它返回一个正确的值。IOIO

而且,当然你可以用不那么冗长的方式来解决这个特殊的问题:

test'' = bar 20

PS:请不要IO无缘无故将东西退回monad。您正在使完全纯正的操作看起来不纯,并且没有合理的返回方式。

于 2015-09-18T08:29:39.073 回答
1

IO为了简洁起见,您将需要一个辅助函数来从元组Int内部 解除Num a => a

(bar 1, bar 2) :: (IO Int, IO Int)

因此您需要带有签名的东西

liftTuple :: (IO x, IO y) -> IO (x, y)
liftTuple (mx, my) = ...

然后你可以做(x,y) <- liftTuple (bar 1, bar 2)

于 2015-09-18T08:15:55.180 回答