【问题标题】:Different Return Types of `print` and `withFile``print` 和 `withFile` 的不同返回类型
【发布时间】:2014-06-17 02:19:14
【问题描述】:

Haskell 的withFile 用给定的IOMode 打开一个文件,然后应用一个函数Handle -> IO r。最终,它返回一个类型IO r

Prelude> import System.IO

Prelude System.IO> :t withFile
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r

print 采用派生Showa,然后返回IO () 的类型。

Prelude System.IO> :t print
print :: Show a => a -> IO ()

IO rIO () 之间的显着区别是什么?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    IO 是一种类型* -> *,也就是说它接受一个类型参数。通常,IO 表示可以执行 I/O 并产生结果的一元操作。给IO 的类型参数决定了结果的类型。因此,

    1. IO () 是一个可以执行 I/O 并产生 () 的单子操作。 () 只有一个值,因此它不传达任何信息。由于它不传达任何信息,因此它的使用方式通常与使用 void 作为传统过程编程语言中的返回值相同。

    2. IO r 是一个可以执行 I/O 并产生 r 的单子操作。您可能会注意到与上述语句的相似之处。区别在于r 不是像() 这样的具体类型,而是一个类型变量

    让我详细说明这意味着什么以及由此产生的后果。看id的类型:

    ghci> :t id
    id :: a -> a
    

    当然,这意味着如果给id 提供a 类型的参数,它将返回相同类型a 的结果。现在检查const ()的类型:

    ghci> :t const ()
    const () :: a -> ()
    

    如果我们给它一个a,它将返回一个()类型的结果。现在检查error

    ghci> :t error
    error :: String -> a
    

    我们必须给它一个String,但它的返回值可以适应我们需要的任何值。当然,由于我们不一定能构造任何给定类型的值,这意味着唯一可能的定义是从不返回一个值,这就是error 所做的。

    因此,有了这种理解,您应该意识到虽然 IO r 始终表示“可以执行 I/O 并返回 r 类型的值的单子操作”,但其含义可能会因出现位置而异在类型签名中。让我们看看你的具体例子:

    ghci> :t withFile
    withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
    

    如果我们有一个函数返回 IO r 而没有出现在其他任何地方的 r,我们可以得出的唯一结论是 IO 绝不能产生值,否则我们将无法声称它可以返回任意的r。幸运的是,情况并非如此:确实出现了另一个rwithFile 接受一个返回 IO r 的函数。因为withFile 产生一个IO,它创建一个r,它知道如何创建一个r 的唯一方法是通过我们给它的函数,我们知道如果它要终止,它必须执行我们给它的功能至少一次。此外,我们知道它必须返回它从中获得的 rs 之一。

    所以,在withFile 的上下文中,IO r 意味着如果你给它一个函数来产生一个返回某些特定类型的一元动作,withFile 最终会给你一个产生该类型的一元动作, 也。举个具体的例子:

    myInt <- withFile "number.txt" ReadMode (fmap read . hGetContents)
    print (myInt + (1 :: Int))
    

    hGetContents,给定一个Handle,将返回一个IO Stringfmap read 将把它变成IO Int。由于withFile 是使用类型变量定义的,没有指定任何特定的具体类型,因此它将适应我们给它的类型(Handle -&gt; IO Int)并返回IO Int。然后我们可以在do 符号块中使用&lt;- 来执行它并将myInt 绑定到结果。返回值通过withFile冒泡。

    【讨论】:

    • myInt sn-p 没有用于读取的类型注释,因此如果没有更多上下文,它可能会默认为Integer 而不是Int。或者更糟糕的是,如果您尝试将其放入 GHCi 以便单独推断行,那么第一行将默认读取(),而第二行将给出错误。
    • 你怎么知道The fmap read will turn that into an IO Int.? Haskell 的read 是否知道如何将内容读取为Int(或Integer)?
    • @ØrjanJohansen:哎呀,你是对的!我的意思是将1 文字注释为明确地成为Int。不过,您关于 GHCi 的观点仍然成立。
    • @Kevin: 是的,readRead 类型类的一部分,所以read 知道如何读取该类的任何实例的内容。由于Int 有一个Read 的实例,read 知道如何读取它。
    【解决方案2】:

    IO r 表示执行有副作用的动作后,会在其中包含一些值r

    IO 结果类型为() 的操作可以与其他语言中的void 进行比较。 (实际上是一个空元组(),其类型也是())。

    IO rIO () 的区别在于,第二种情况在执行有副作用的动作后,其内部不包含任何值。这表明您执行IO 操作的唯一目的是为了实现它的副作用,例如打印到屏幕上。而在IO r 中,在执行带有副作用的操作后,它还包含一些包装在IO 中的值,这些值随后可以在您的程序中使用。


    即使withFile 函数也可以返回IO (),因为r 只是() 的更一般形式,具体取决于传递给它的高阶函数的类型:

    test = withFile "testFile" ReadMode (\handle ->
                                               do
                                                 str <- hGetContents handle
                                                 print str
                                               )
    

    检查其类型:

    ghci> :t test
    test :: IO ()
    

    【讨论】:

      【解决方案3】:

      IO r 是比IO () 更通用的类型。

      r 是一个变量,可以是任何类型。

      如果r == (),那么IO r == IO ()

      【讨论】:

        猜你喜欢
        • 2020-08-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-13
        • 1970-01-01
        • 1970-01-01
        • 2017-10-21
        • 2017-05-24
        相关资源
        最近更新 更多