考虑
ioFunction :: String -> IO ()
ioFunction str = do putStrLn str
putStrLn "2"
在这里,从数学的角度来看,ioFunction
是一个函数,它接受一个输入并返回类型IO ()
的值。我认为这意味着什么。也就是说,从数学的角度来看,这个函数返回一个值,而不是别的;特别是,它不会打印任何东西。
这是否意味着Haskell使用IO monad进行命令性副作用的方式(在这种情况下:运行此函数将首先打印str
然后按顺序打印“2”)< strong>纯粹的GHC实现细节与术语的数学(在某种意义上甚至是Haskell)无关?
编辑:为了使这个问题更清楚,我的意思是,例如,以下两个函数之间的数学观点有什么不同:
ioFunction1 :: String -> IO ()
ioFunction1 str = do putStrLn str
putStrLn "2"
ioFunction2 :: String -> IO ()
ioFunction2 str = do return ()
似乎答案是否定的,因为 - 从数学的角度来看 - 它们都将String
作为输入并返回IO ()
类型的值(可能是相同的值) 。情况不是这样吗?
答案 0 :(得分:5)
在这里,从数学的角度来看,
ioFunction
是一个函数,它接受一个输入并返回类型IO ()
的值。我认为这意味着什么。
是。准确。
这是否意味着Haskell使用
IO
monad进行命令性副作用的方式(在这种情况下:运行此函数将首先打印str然后print "2"
,按此顺序)是纯粹是一个GHC实现细节,与术语的数学(在某种意义上甚至是Haskell)无关?
不完全。从数学(集合论)的角度来看,我认为“相同”意味着结构相同。由于我不知道IO ()
的值是什么,我无法说明该类型的两个值是否相同。
事实上,这是设计上的:通过使IO
不透明(在我不知道什么构成IO
的意义上),GHC阻止我永远不能说那个类型IO ()
的值等于另一个。我可以通过IO
,fmap
,(<*>)
,(>>=)
等功能公开mplus
所能解决的问题。
答案 1 :(得分:4)
我总是觉得考虑IO
monad的简化“命令式语言源代码”实现很有帮助:
data IO :: * -> * where
PutStr :: String -> IO ()
GetLine :: IO String
IOPure :: a -> IO a
IOSeq :: IO a -> IO b -> IO b
...
LaunchMissiles :: IO ()
那么ioFunction
在数学意义上显然是一个恰当的,合理的函数:
ioFunction str = do putStrLn str
putStrLn "2"
= putStr (str++"\n") >> putStrLn "2\n"
= IOSeq (PutStr (str++"\n")) (PutStrLn "2\n")
这只是一个有效表示命令式语言的一些源代码的数据结构。 ioFunction
将给定的参数放在结果结构中的特定位置,因此它在数学上不仅仅是一个简单的“return ()
而且没有别的(可能会发生某些事情,但它是一个GHC实现细节) )”
ioFunction2
的值确实完全不同:
ioFunction2 str = do return ()
= return ()
= IOPure ()
我们怎么知道他们在这个意义上是不同的?
这是一个很好的问题。实际上,你通过执行两个程序并观察它们会产生不同的效果来了解它,因此它们must have been different。这当然有点尴尬 - “观察发生的事情”不是数学,而是物理学,观察将科学地要求你在完全相同的环境条件下执行两次。
更糟糕的是,即使纯数值通常在数学上是相同的,您也可以观察到不同的行为:print 100000000
会立即引起副作用,而print $ last [0..100000000]
则会产生明显的停顿(其中,如果你用时间打印命令跟着它,实际上可以产生不同的文本输出。)
但这些问题在现实世界中是不可避免的。因此,只有Haskell没有在IO上指定任何可以用数学严谨性来检查语言中的相等性的结构。所以,在某种意义上,你真的不能知道putStrLn str >> putStrLn "2"
与return ()
不一样。
实际上他们可能是一样的。可以想象的是,IO
比以上更简单的玩具实现:
data IO :: * -> * where
IONop :: IO ()
IOHang :: IO a -> IO a
并简单地将任何纯输出操作映射到no-op(以及任何无限循环的输入)。那么你会有
ioFunction str = do putStrLn str
putStrLn "2"
= IONop >> IONop
= IONop
和
ioFunction2 str = return ()
= IONop
这有点像我们在自然数上强加了一个额外的公理,即1 = 0.然后你可以得出结论每个数字为零。
显然,这将是一个完全无用的实现。但是,如果你在沙盒环境中运行Haskell代码并且想要完全确定没有任何不好的事情发生,那么类似的东西实际上是有意义的。 http://tryhaskell.org/做了类似的事情。
答案 2 :(得分:0)
为简单起见,让我们专注于IO monad的输出方面。从数学上讲,输出(作者)monad由endofunctor $ T $给出,$ T(A)= U ^ * \ times A $,其中$ U $是一组固定的字符,$ U ^ * $是超过$ U $的字符串集。然后$ ioFunction:U ^ * \到T()$,即$ ioFunction:U ^ * \到U ^ * \ times()$,和$ ioFunction(s)=(s ++“\ n”++“2 \ N”,())$。相比之下,$ ioFunction2(s)=(“”,())$。任何实现都必须尊重这种差异。差异主要是数学上的,而不是实现细节。 (嗯,TeX标记似乎只适用于mathoverflow ...)