【问题标题】:fmap into a do block fails with a print errorfmap 进入 do 块失败并出现打印错误
【发布时间】:2019-09-15 03:52:58
【问题描述】:

我试图理解为什么我用 do-block 编写的函数不能重写为在列表上 fmap 类似的 lambda 表达式。

我有以下几点:

-- This works
test1 x = do 
        let m = T.pack $ show x
        T.putStrLn m

test1 1

生产

1

但是

-- This fails
fmap (\x -> do 
              let m = T.pack $ show x
              T.putStrLn m
              ) [1..10]

-- And this also fails
fmap (\x -> do 
             T.putStrLn $ T.pack $ show x
                ) [1..10]

有错误:

<interactive>:1:1: error:
    • No instance for (Show (IO ())) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it

我的 putStrLn 在工作和非工作之间是一致的。进口是一样的。我打印所需的 show-pack-putstrln 舞蹈在工作和非工作之间也是一致的。

打印的使用在工作和非工作之间发生变化是怎么回事?

更新 1

-- I was also surprised that this fails
fmap (T.putStrLn $ T.pack $ show) [1..10]
-- it seemed as similar as possible to the test1 function but mapped.

<interactive>:1:7: error:
    • Couldn't match expected type ‘Integer -> b’ with actual type ‘IO ()’
    • In the first argument of ‘fmap’, namely ‘(T.putStrLn $ pack $ show)’
      In the expression: fmap (T.putStrLn $ pack $ show) [1 .. 10]
      In an equation for ‘it’: it = fmap (T.putStrLn $ pack $ show) [1 .. 10]
    • Relevant bindings include it :: [b] (bound at <interactive>:1:1)
<interactive>:1:29: error:
    • Couldn't match type ‘() -> String’ with ‘String’
      Expected type: String
        Actual type: () -> String
    • Probable cause: ‘show’ is applied to too few arguments
      In the second argument of ‘($)’, namely ‘show’
      In the second argument of ‘($)’, namely ‘pack $ show’
      In the first argument of ‘fmap’, namely ‘(T.putStrLn $ pack $ show)’

更新 2

-- This lambda returns x of the same type as \x
-- even while incidentally printing along the way
fmap (\x -> do 
              let m = T.pack $ show x
              T.putStrLn $ m
              return x
              ) [1..10]

但也失败了:

<interactive>:1:1: error:
    • No instance for (Show (IO Integer)) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it

【问题讨论】:

    标签: haskell monads io-monad do-notation


    【解决方案1】:

    fmap f [1..10] 的类型是[T],其中Tf 的返回类型。

    在你的情况下,T = IO (),所以完整表达式的类型是[IO ()]

    IO 操作无法打印,因此当您尝试打印该列表时 GHCi 会报错。您可能希望使用sequence_ (fmap f [1..10]) 之类的方式运行这些操作而不是打印它们。

    或者,考虑放弃fmap,而是使用类似的东西

    import Data.Foldable (for_)
    
    main = do
       putStrLn "hello"
       for_ [1..10] $ \i -> do
          putStrLn "in the loop"
          print (i*2)
       putStrLn "out of the loop"
    

    【讨论】:

    • 谢谢,这很有帮助,但是当我将 lambda 的返回类型更改为与我在更新 2 中以 \x 形式出现的 x 相同时,只需执行打印为偶然而不是返回,我仍然得到同样的错误。我知道你的 for_ 循环是正确的,但为什么我修改后的 fmap 不能像我在上面替换的函数那样工作?
    • @Mittenchops fmap 在列表中生成一个列表。您的原始代码不涉及任何列表。尝试向 GHCi 询问表达式的类型,您会发现它们的类型是 [IO ()] 而不是 IO something,因此如果您想实际运行 IO 操作列表,则需要 sequence_
    【解决方案2】:

    你写道:

    但是当我将 lambda 的返回类型更改为与以 \x 形式出现的 x 相同时,就像我在更新 2 中所做的那样......

    不,不。你没有。 lambda 函数返回其最后一个表达式的值。您的 lambda 函数只有 一个 表达式 - 整个 do { ... } 定义了一个值,即 lambda 函数的返回值。不是xreturn 属于 do,而不是 lambda 表达式。如果我们用显式分隔符来写它更容易看到,如

    fmap (\x -> do {
                  let m = T.pack $ show x ;
                  T.putStrLn $ m ;
                  return x
                  } ) [1..10]
    

    do 块作为一个整体具有与其每个行语句相同的一元类型。

    其中一个是putStrLn ...,其类型是IO ()。因此,您的 lambda 函数会为某些 t 返回 IO t

    因为return xtx 的类型。我们有return :: Monad m =&gt; t -&gt; m t,所以m ~ IOreturn :: t -&gt; IO t

    x 来自参数列表Num t =&gt; [t],所以总的来说你有

    Num t => fmap (fx :: t -> IO t) (xs :: [t]) :: [IO t]
    

               xs :: [t]
            fx    ::  t  ->  IO t
       ----------------------------
       fmap fx xs ::        [IO t]
    

    【讨论】:

    • 这是一个非常有用的解释。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2018-02-24
    • 2017-04-12
    • 2012-09-24
    • 2016-03-12
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多