为什么我不能在这里放一个打印函数语句?

Why can't I put a print function statement here?

我正在尝试使用 try-catch 块执行以下代码:

import System.Environment  
import System.IO  
import System.IO.Error  
import Control.Exception

isBinary :: String -> Bool
isBinary ss = do 
    print "In isBinary fn"   -- works if this line is removed.
    let ans = any (\c -> ord c > 127) ss
    ans

toTry :: String -> IO ()  
toTry firline = do
        print "In toTry fn."
        let answer = isBinary firline
        if not answer then do
            print "Sent line not binary: "
        else
            print "Sent line binary"

handler :: IOError -> IO ()  
handler e = putStrLn "Whoops, had some trouble!"  

ss = "this is a test"
main = do 
    toTry ss `catch` handler

但是,我收到以下错误:

$ runghc trycatch3.hs 

trycatch3.hs:9:9: error:
    • Couldn't match expected type ‘Bool’ with actual type ‘IO Bool’
    • In a stmt of a 'do' block: print "in isBinary fn"
      In the expression:
        do { print "in isBinary fn";
             let ans = any (\ c -> ...) ss;
             return ans }
      In an equation for ‘isBinary’:
          isBinary ss
            = do { print "in isBinary fn";
                   let ans = ...;
                   return ans }

trycatch3.hs:10:30: error:
    • Variable not in scope: ord :: Char -> Integer
    • Perhaps you meant one of these:
        ‘or’ (imported from Prelude), ‘odd’ (imported from Prelude)

如果从 isBinary 函数中删除 print 语句,错误就会消失,程序运行良好。

为什么我不能在这个函数中放入打印语句?

您可能会对在 toTry 中工作的相同打印行感到困惑,但在 isBinary 中却不会。差异源于声明:

isBinary :: String -> Bool

这意味着 isBinary 是一个纯函数(即没有副作用),接受一个字符串并返回一个布尔值。事实上,您可以将其简化为

isBinary ss = any (\c -> ord c > 127) ss 

甚至使用 point-free 样式

isBinary = any (\c -> ord c > 127)

然而,toTry

toTry :: String -> IO ()

即它需要一个字符串和 returns 不纯的 IO monad(可能有副作用,例如将文本打印到控制台)。

Haskell 是一种鼓励使用纯函数的语言,并通过强制程序员显式标记不纯代码来强制使用类型系统。

延伸阅读: What does "pure" mean in "pure functional language"?

答案是,"because types"。具体来说:

isBinary :: String -> Bool
isBinary ss = do 
  ....

因为它是一个 do 块,所以 isBinary 的 return 类型必须 匹配一些单子类型 Monad m => m t m 和一些 t。这里,由于 print "" :: IO ()mIO,所以应该是

isBinary :: String -> IO Bool
isBinary ss = do 

现在

    print "In isBinary fn"                 -- works
    let ans = any (\c -> ord c > 127) ss   -- also works
    ans                                    -- doesn't work

ans 再次因为类型而不起作用。它的类型是 Bool,但它必须是 IO Bool——首先,因为这个 do 块属于 IO monad,因为 print;其次,由于整个函数的 return 类型。

改为使用

    return ans

现在它可以工作了,因为 return 将一个值注入到 monadic 上下文中,并且作为最后一个 do 块值,它成为 do 块产生的值整体(如果 return val 出现在中间,它只是将 val 传递到组合计算的下一步)。

必须扩充函数 toTry 才能使用新定义:

toTry :: String -> IO ()  
toTry firline = do
        print "In toTry fn."
        -- let answer = isBinary firline    -- incorrect, now!
        answer <- isBinary firline          -- isBinary ... :: IO Bool
        if not answer then do               --       answer ::    Bool
            print "Sent line not binary: "
        else
            print "Sent line binary"

m a<-右边,a在左边。

有关 do 表示法的一般说明,请参阅

查看您的代码,您在 isBinary 中对 print 的使用似乎不是您希望函数执行的操作的组成部分,而只是将被删除的调试打印语句稍后的。在这种情况下,您不想将 isBinary 的类型更改为 String -> IO Bool(有关更多信息,请参阅 ), as you don't actually need IO except for debugging. Rather, the core libraries offer the Debug.Trace module,它迎合了这种情况。有了它,我们可以像这样添加调试打印语句:

isBinary :: String -> Bool
isBinary ss = trace "In isBinary fn" $ any (\c -> ord c > 127) ss

然后,完成调试后,您可以删除 trace 的使用——值得重复,您确实应该稍后再这样做。引用 Debug.Trace 文档:

Functions for tracing and monitoring execution.

These can be useful for investigating bugs or performance problems. They should not be used in production code.