Haskell “print”和“withFile”的不同返回类型`

Haskell “print”和“withFile”的不同返回类型`,haskell,Haskell,Haskell的withFile打开一个带有给定IOMode的文件,然后应用函数Handle->IO r。最后,它返回一个类型ior Prelude> import System.IO Prelude System.IO> :t withFile withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r print获取派生Show的a,然后返回一种类型的IO() IO r和IO()之间的显著区别是

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

Prelude> import System.IO

Prelude System.IO> :t withFile
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
print
获取派生
Show
a
,然后返回一种类型的
IO()


IO r
IO()
之间的显著区别是什么?

IO
是一种
*->*
类型,也就是说它需要一个类型参数。一般来说,
IO
表示一个可以执行I/O并产生结果的一元操作。给定给
IO
的type参数确定结果的类型。所以,

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

  • IO r
    是一种可以执行I/O并产生
    r
    的一元操作,您可能会注意到与上述语句类似。不同之处在于,
    r
    不是像
    ()
    那样的具体类型,而是一个类型变量

  • 让我进一步阐述这意味着什么以及由此产生的后果。查看
    id
    的类型:

    ghci> :t id
    id :: a -> a
    
    当然,这意味着如果给
    id
    一个
    a
    类型的参数,它将返回一个与
    a
    类型相同的结果。现在检查
    const()
    的类型:

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

    ghci> :t error
    error :: String -> a
    
    我们必须给它一个
    字符串
    ,但它的返回值可以适应我们需要的任何内容。当然,由于我们不必构造任何给定类型的值,这意味着唯一可能的定义是永远不返回值,这就是
    error
    所做的

    因此,有了这样的理解,您应该认识到,虽然
    IO r
    始终意味着“可以执行I/O并返回
    r
    类型值的一元操作”,但其含义可能会因其在类型签名中的位置而异。让我们看一下您的特定示例:

    ghci> :t withFile
    withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
    
    如果我们有一个函数返回一个
    IO r
    ,而没有
    r
    出现在其他任何地方,那么我们可以得出的唯一结论是
    IO
    决不能产生一个值,否则我们就不能声称它可以返回任意的
    r
    。幸运的是,情况并非如此:另一个
    r
    确实出现了
    withFile
    接受一个返回
    IO r
    的函数。由于
    withFile
    生成一个创建
    r
    IO
    ,并且它知道如何创建
    r
    的唯一方法是通过我们给它的函数,我们知道如果它要终止,它必须至少执行一次我们给它的函数。此外,我们知道它必须返回它从中得到的
    r
    s之一

    因此,在
    withFile
    的上下文中,
    ior
    意味着,如果给它一个函数,该函数产生一个返回特定类型的一元操作,
    withFile
    也会给你一个产生该类型的一元操作。作为一个具体例子:

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

    myInt-IO-Int
    )并返回一个
    IO-Int
    。然后我们可以使用
    IO
    是一种
    *->*
    类型,也就是说它需要一个类型参数。一般来说,
    IO
    表示一个可以执行I/O并产生结果的一元操作。给定给
    IO
    的type参数确定结果的类型。所以,

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

  • IO r
    是一种可以执行I/O并产生
    r
    的一元操作,您可能会注意到与上述语句类似。不同之处在于,
    r
    不是像
    ()
    那样的具体类型,而是一个类型变量

  • 让我进一步阐述这意味着什么以及由此产生的后果。查看
    id
    的类型:

    ghci> :t id
    id :: a -> a
    
    当然,这意味着如果给
    id
    一个
    a
    类型的参数,它将返回一个与
    a
    类型相同的结果。现在检查
    const()
    的类型:

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

    ghci> :t error
    error :: String -> a
    
    我们必须给它一个
    字符串
    ,但它的返回值可以适应我们需要的任何内容。当然,由于我们不必构造任何给定类型的值,这意味着唯一可能的定义是永远不返回值,这就是
    error
    所做的

    因此,有了这样的理解,您应该认识到,虽然
    IO r
    始终意味着“可以执行I/O并返回
    r
    类型值的一元操作”,但其含义可能会因其在类型签名中的位置而异。让我们看一下您的特定示例:

    ghci> :t withFile
    withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
    
    如果我们有一个函数返回一个
    IO r
    ,而没有
    r
    出现在其他任何地方,那么我们可以得出的唯一结论是
    IO
    决不能产生一个值,否则我们就不能声称它可以返回任意的
    r
    。幸运的是,情况并非如此:另一个
    r
    确实出现了
    withFile
    使用一个函数
    ghci> :t test
    test :: IO ()