Haskell中IO的签名(这个类或数据?)

时间:2013-08-18 02:47:38

标签: haskell

问题不是IO的作用,而是如何定义它的签名。具体来说,这个数据或类是“a”的类型参数吗?我没找到任何地方。另外,我不明白这个的句法意义:

f :: IO a

6 个答案:

答案 0 :(得分: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也是MonadFunctor

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

答案 1 :(得分:4)

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

考虑一下这个简单的清单。如果我们要编写自己的Int列表,我们可以这样做:

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

如果你抽象出它是什么元素类型,你得到这个:

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

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

以类似的方式,IO是具有种类* -> *的多态类型,这又意味着它需要一个类型参数。如果你自己定义它,它可能看起来像这样:

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

(定义由this answer提供)

答案 2 :(得分: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 #))

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

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

所以基本上IO a是一个存储一个函数的数据结构,它接受旧的现实世界,并通过io操作和a返回新的现实世界。

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

IO类型是一个抽象newtype - 构造函数未导出,因此您无法绕过库函数,直接使用它并执行令人讨厌的事情:复制RealWorld,创建RealWorld没有或逃避monad(写一个IO a -> a类型的函数)。

答案 3 :(得分:2)

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

如果你有一个带有类型a的对象,那么它可以被'wrappered'作为类型IO a的对象,你可以将其视为一个给出类型为a的对象的动作。例如,getChar的类型为IO Char,因此在调用时,它具有(从程序的角度来看)生成一个来自stdin的字符的副作用。

作为另一个例子,putChar的类型为Char - > IO(),意味着它接受一个char,然后执行一些不输出的动作(在程序的上下文中,虽然它会打印给stdout的char)。

编辑:monad的更多解释:

Monad可以被认为是“包装类型”M,并且具有两个相关的功能:
return和>> =。

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

因此,

返回类型为a - >嘛。返回尝试不改变传递的元素 - 如果你调用return x,你将得到一个包含x的所有信息的x的包装版本(理论上,至少。这不会发生,例如,空monad。)

例如,返回“x”将产生M个字符。这就是getChar的工作原理 - 它使用return语句产生一个IO Char,然后使用< - 。

将其从包装器中拉出。

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

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

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

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

现在,对于某个monad来说,这些操作也必须有一定的关系 - 它们的定义不够。

首先:(返回x)>> = f必须等于f x。也就是说,它必须等同于在x上执行操作,无论它是否被“包裹”在monad中。

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

第三,最后(x>> = = f)>> = g必须等于x>> =(\ y - >(f y>> = 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。

对不起文字的墙,我希望它能解释一下。如果你需要更多关于这一点,或者这个解释中的某些内容令人困惑,那就问一下。

答案 4 :(得分:1)

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

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

如果查看IO类型构造函数的定义,您将看到它是一个定义为的新类型:GHC.Types.IO(GHC.Prim.State#GHC.Prim.RealWorld - &gt;(#GHC.Prim .State#GHC.Prim.RealWorld,a#))

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

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

答案 5 :(得分:0)

数据IO a a的含义与Maybe a中的含义基本相同。 但我们不能摆脱构造函数,例如:

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

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

{-# LANGUAGE ScopedTypeVariables #-}
foo = do
   (fromIO :: a) <- (dataIO :: IO a)
    ...