只是为了学习我在我的控制台WinGHCi上做了这个:
let ls = [putChar 'x',putChar 'y']
然后如果:
head ls
输出是显而易见的 x (从某种意义上说,我不明白为什么)
否则,如果我这样做:
tail ls
我收到此错误:
使用'print'
时没有(Show(IO()))的实例在交互式GHCi命令的stmt中:打印它
为什么呢?不应该输出 y 或[putChar 'y']
?
答案 0 :(得分:13)
此行为已解释为here。
head ls
的类型为IO ()
,因此GCHi会执行操作,但不会打印结果()
。
相比之下,tail ls
的类型为[IO ()]
。由于这不是IO
操作,GCHi会尝试使用print
来显示它,其类型为:
print :: Show a => a -> IO ()
但是,由于Show
没有[IO ()]
个实例,您会收到错误消息。如果您想评估可以使用sequence_
:
sequence_ (tail ls)
答案 1 :(得分:6)
好吧,actually not obviously!事实上,然后如果:
head ls
输出是显而易见的x
head ls
并不是与角色'x'
直接相关的东西。首先,它只是一个IO
动作,GHCi假设这个动作做了你想要发生的事情:它有一个特殊的处理方式,对于IO
动作,它试图不显示该动作,但执行它。嗯,很好,你没有考虑清单[launchMissiles, putStrLn "oops"]
......
现在,执行操作确实会导致将字符'x'
打印到控制台,这恰好也是您的视口,因此您可以将其视为仅仅是putChar 'x'
的值。但事实并非如此!
OTOH,tail ls
来自外部只是一个列表,所以ghci并不假设您想要执行任何操作。它确实尝试显示列表,但您无法显示 IO操作或其中包含IO操作的任何操作;这是错误信息所说的内容。
答案 2 :(得分:5)
当你有
时> let ls = [putChar 'x', putChar 'y']
您没有执行存储在该列表中的操作,您只是构建这些操作,如果这有意义的话。你可以有像
这样的东西printHelp :: IO ()
printHelp = putStrLn "You need to pass XYZ options to this program"
在文件中定义。这不会立即打印出消息,而是构造一个动作,一旦执行,就会将该消息打印到控制台。执行发生在两个地方之一:main
函数和GHCi。
看起来你正在使用GHCi来执行代码,这对于探索不同的函数来说非常棒,但与在文件中执行代码有点不同。每当您在GHCi中自行评估IO Something
操作时,它就会运行该IO操作。每当您评估GHCi中的任何其他值时,它会在该行前面插入print
。所以当你这样做时
> head ls
此类型为IO ()
,因此执行操作putChar 'x'
,导致x
打印到控制台。当你这样做
> tail ls
这个类型为[IO ()]
,因此GHCi会尝试在其前面添加print
,使其等同于
> print (tail ls)
print
函数的类型为
print :: (Show a) => a -> IO ()
我们已在此处向print
传递了一个列表,并且列表中有Show
的实例,但仅当列表的元素具有Show
的实例时。因此编译器会尝试查看IO ()
是否有Show
的实例。不幸的是,它没有,因为IO
是一个非常复杂的类型,表示几乎任何事情都可能发生。因此,编译器显示IO ()
没有Show
实例的错误。
如果您希望按顺序运行列表中的每个操作,则有一个函数:Control.Monad.sequence
(如果您不关心结果,则为Control.Monad.sequence_
)。此功能实际上适用于所有Monad
,而不仅仅是IO
:
> sequence_ (tail ls)
y> sequence_ ls
xy>
答案 3 :(得分:0)
不仅tail ls
,如果您执行ls
或init ls
,您将收到相同的错误。
ls
的类型是:
ls :: [IO ()]
当您在GHCi中键入内容时,它将评估表达式并打印结果。
在第二种情况(ls
)中,ls
类型的返回值是一系列操作,GHC
不知道如何操作显示IO ()
的操作。
在第一种情况下,GHCi评估打印x的IO ()
。 IO ()
有无返回值,因此无需显示任何内容且无错误。