5

问题不是做什么IO,而是它是如何定义的,它的签名。具体来说,这个是数据还是类,a那么“”是它的类型参数吗?我在任何地方都没有找到它。另外,我不明白这个的语法含义:

f :: IO a
4

6 回答 6

12

您问是否IO a是数据类型:它是。你问是否a是它的类型参数:它是。你说你找不到它的定义。让我告诉你如何找到它:

localhost:~ gareth.rowlands$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Prelude> :i IO
newtype IO a
  = GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld
                  -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))
    -- Defined in `GHC.Types'
instance Monad IO -- Defined in `GHC.Base'
instance Functor IO -- Defined in `GHC.Base'
Prelude> 

在 ghci 中,:i或者:info告诉你一个类型。它显示类型声明及其定义位置。你可以看到那IO是aMonad和a Functor

这种技术对普通的 Haskell 类型更有用——正如其他人所指出的,IO这在 Haskell 中很神奇。在典型的 Haskell 类型中,类型签名非常具有启发性,但重要的IO不是它的类型声明,而是IO实际执行的操作IO。他们以非常传统的方式执行此操作,通常通过调用底层 C 或 OS 例程。例如,Haskell 的putChar动作可能会调用 C 的putchar函数。

于 2013-08-18T06:33:37.797 回答
4

IO是一个多态类型(恰好是 的一个实例Monad,在这里无关)。

考虑一下不起眼的清单。如果我们要编写自己的Ints 列表,我们可以这样做:

data IntList = Nil | Cons { listHead :: Int, listRest :: IntList }

如果你然后抽象它是什么元素类型,你会得到:

data List a = Nil | Cons { listHead :: a, listRest :: List a }

如您所见, 的返回listRest值为List aList是 kind 的多态类型,* -> *也就是说它需要一个类型参数来创建具体类型。

以类似的方式,IO是一个带有 kind 的多态类型* -> *,这再次意味着它接受一个类型参数。如果您要自己定义它,它可能如下所示:

data IO a = IO (RealWorld -> (a, RealWorld))

(定义由这个答案提供)

于 2013-08-18T02:53:36.903 回答
3

魔法的数量IO被严重高估了:它得到了编译器和运行时系统的一些支持,但比新手通常期望的要少得多。

这是定义它的源文件:

http://www.haskell.org/ghc/docs/latest/html/libraries/ghc-prim-0.3.0.0/src/GHC-Types.html

newtype IO a
  = IO (State# RealWorld -> (# State# RealWorld, a #))

它只是 state monad 的优化版本。如果我们删除优化注释,我们将看到:

data IO a = IO (Realworld -> (Realworld, a))

所以基本上IO a是一个存储一个函数的数据结构,该函数采用旧的现实世界并返回新的现实世界并执行 io 操作a

一些编译器技巧主要是为了Realworld有效地删除虚拟值。

IO 类型是抽象的newtype- 构造函数不会被导出,所以你不能绕过库函数,直接使用它并执行令人讨厌的事情:重复RealWorldRealWorld无中生有或逃避 monad(编写IO a -> a类型的函数)。

于 2013-08-18T16:01:20.663 回答
2

由于 IO 可以应用于任何类型 a 的对象,因为它是一个多态 monad,所以没有指定 a。

如果您有一些类型为 a 的对象,那么它可以被“包装”为类型 IO a 的对象,您可以将其视为提供类型 a 的对象的操作。例如,getChar 是 IO Char 类型,因此当它被调用时,它具有(从程序的角度)生成一个来自 stdin 的字符的副作用。

再举一个例子,putChar 的类型是 Char -> IO (),这意味着它接受一个 char,然后执行一些不提供输出的操作(在程序的上下文中,尽管它会将给定的 char 打印到 stdout)。

编辑:单子的更多解释:

monad 可以被认为是一个“包装类型”M,并且有两个相关的函数:
return 和 >>=。

给定类型 a,可以使用 return 函数创建类型为 M a 的对象(在 IO monad 的情况下为 IO a)。

因此,return 的类型为 a -> M a。此外,return 试图不改变它传递的元素——如果你调用 return x,你将得到一个包含 x 的所有信息的 x 的包装版本(至少理论上,这不会发生,例如,空单子。)

例如,返回“x”将产生一个 M 字符。这就是 getChar 的工作原理——它使用 return 语句生成一个 IO Char,然后用 <- 将其从包装器中拉出。

>>=,读作'bind',更复杂。它的类型是 M a -> (a -> M b) -> M b,它的作用是获取一个“包装”对象,以及从该对象的底层类型到另一个“包装”对象的函数,并应用该函数到第一个输入中的基础变量。

例如, (return 5) >>= (return . (+ 3)) 将产生一个 M Int,这将与 return 8 给出的 M Int 相同。这样,任何可以在外部应用的函数也可以在其中应用 monad。

为此,可以采用任意函数 f :: a -> b,并给出新函数 g :: M a -> M b,如下所示:

g x = x >>= (return . f)

现在,要成为 monad,这些操作也必须有一定的关系——它们上面的定义还不够。

首先: (return x) >>= f 必须等价于 f x。也就是说,无论 x 是否“包装”在 monad 中,它都必须等效于对 x 执行操作。

第二:x >>= return 必须等价于 m。也就是说,如果一个对象被 bind 解包,然后通过 return 重新包装,它必须返回到相同的状态,保持不变。

第三,最后 (x >>= f) >>= g 必须等价于 x >>= (\y -> (fy >>= g) )。也就是说,函数绑定是关联的(有点)。更准确地说,如果两个函数被先后绑定,这必然相当于绑定了它们的组合。

现在,虽然这是 monad 的工作方式,但它不是最常用的方式,因为 do 和 <- 的语法糖。

本质上,do 开始了一个长长的绑定链,每个 <- 都会创建一个被绑定的 lambda 函数。

例如,

a = do x <- something
       y <- function x
       return y

相当于

a = something >>= (\x -> (function x) >>= (\y -> return y))

在这两种情况下,某些东西都绑定到 x,函数 x 绑定到 y,然后 y 在相关 monad 的包装器中返回给 a。

对不起,文字墙,我希望它能解释一些东西。如果您需要对此进行澄清,或者此解释中的某些内容令人困惑,请询问。

于 2013-08-18T02:57:40.547 回答
1

如果你问我,这是一个非常好的问题。我记得我也对此感到非常困惑,也许这会有所帮助......

'IO'是一个类型构造函数,'IO a'是一个类型,'a'(在'IO a'中)是一个类型变量。字母“a”没有意义,字母“b”或“t1”也可以使用。

如果查看 IO 类型构造函数的定义,您会发现它是一个新类型,定义为:GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC .Prim.RealWorld, a #))

'f :: IO a' 是一个名为 'f' 的函数的类型,显然没有参数,它返回 IO monad 中某些不受约束的类型的结果。'in the IO monad' 意味着 f 可以在计算结果时执行一些 IO(即更改 'RealWorld',其中 'change' 意味着用新的 RealWorld 替换提供的 RealWorld)。f 的结果是多态的(这是一个类型变量 'a' 而不是像 'Int' 这样的类型常量)。多态结果意味着在您的程序中,调用者决定了结果的类型,因此在一个地方使用 f 可以返回一个 Int,在另一个地方使用它可以返回一个 String。“不受约束”意味着没有类型类限制可以返回的类型,因此可以返回任何类型。

为什么'f'是一个函数而不是一个常数,因为没有参数并且Haskell是纯的?因为 IO 的定义意味着 'f :: IO a' 可以写成 'f :: GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #)' 所以实际上有一个参数——“现实世界的状态”。

于 2013-08-19T13:10:05.110 回答
0

数据IO a a中的含义主要与 中相同Maybe a。但是我们不能摆脱一个构造函数,比如:

fromIO :: IO a -> a
fromIO (IO a) = a

幸运的是,我们可以在 Monads 中使用这些数据,例如:

{-# LANGUAGE ScopedTypeVariables #-}
foo = do
   (fromIO :: a) <- (dataIO :: IO a)
    ...
于 2013-08-18T14:26:42.723 回答