我已经将IO monad描述为状态monad,状态是“现实世界”。这种IO方法的支持者认为,这使IO操作变得纯粹,就像在引用透明时一样。这是为什么?从我的角度来看,IO monad中的代码似乎有很多可观察到的副作用。此外,是不是可以描述几乎任何非纯函数,如现实世界的功能?例如,我们不能想到,比方说,C的malloc是一个函数,它接受一个 RealWorld 和一个Int并返回一个指针和一个 RealWorld ,就像在IO monad中, RealWorld 是隐含的吗?
注意:我知道monad是什么以及它是如何使用的。请不要回复随机monad教程的链接,除非它专门解答我的问题。
答案 0 :(得分:54)
我认为我听过的最好的解释实际上是最近的SO。 IO Foo
是创建Foo
的秘诀。另一种常见的,更直接的说法是,它是一个“产生Foo
”的程序。可以执行(多次)来创建Foo
或尝试死亡。配方/程序的执行是我们最终想要的(否则,为什么要写一个?),但在我们的代码中由IO
动作表示的东西就是配方本身。
该配方是纯粹的价值,与String
纯粹的价值完全相同。食谱可以以有趣的,有时令人惊讶的方式进行组合和操作,但这些食谱可以组合的多种方式(除了公然的非纯unsafePerformIO
,unsafeCoerce
等)都完全参考透明,确定性,以及所有美好的东西。由此产生的配方完全没有任何方式取决于它所构建的配方以外的任何其他状态。
答案 1 :(得分:15)
此外,是不是可以描述几乎任何非纯函数,如现实世界的功能?例如,我们不能想到,比方说,C的malloc是一个函数,它接受一个RealWorld和一个Int并返回一个指针和一个RealWorld,就像在IO monad中一样,RealWorld是隐含的吗?
当然......
函数式编程的整体思想是将程序描述为小型,独立计算的组合,构建更大的计算。
进行这些独立计算后,您将获得许多好处,从简洁的程序到高效且可高效的可并行化代码,懒惰到控制按预期流动的严格保证 - 没有机会任意数据的干扰或腐败。
现在 - 在某些情况下(比如IO),我们需要不纯的代码。涉及此类操作的计算不能独立 - 它们可能会改变另一个计算的任意数据。
重点是 - Haskell 始终是纯粹的,IO
不会改变这一点。
因此,我们不纯的,非独立的代码必须得到一个共同的依赖 - 我们必须通过RealWorld
。因此,无论我们想要运行哪些有状态计算,我们都必须传递此RealWorld
以将我们的更改应用于 - 并且无论其他有状态计算想要查看或进行更改,都必须知道RealWorld
。
这是通过IO
monad显式地还是隐式地完成的,这是无关紧要的。您将Haskell程序构建为一个转换数据的巨型计算,此数据的一部分是RealWorld
。
当您的程序以当前现实世界作为参数运行时,初始main :: IO ()
被调用后,这个真实世界将被执行所有不纯的计算,就像State
中的数据一样。这就是monadic >>=
(bind)所需要的。
RealWorld
没有获得(如纯计算或没有任何>>=
- main
),没有机会对它做任何事情。 获取的地方,通过纯函数传递(隐式)参数而发生的。这就是为什么
let foo = putStrLn "AAARGH" in 42
绝对没有 - 为什么IO
monad - 就像其他任何东西一样 - 是纯粹的。在这段代码中发生的事情当然可能是不纯的,但它全都陷入了内部,没有机会干扰非连接的计算。
答案 2 :(得分:10)
假设我们有类似的东西:
animatePowBoomWhenHearNoiseInMicrophone :: TimeDiff -> Sample -> IO ()
animatePowBoomWhenHearNoiseInMicrophone
levelWeightedAverageHalfLife levelThreshord = ...
programA :: IO ()
programA = animatePowBoomWhenHearNoiseInMicrophone 3 10000
programB :: IO ()
programB = animatePowBoomWhenHearNoiseInMicrophone 3 10000
以下是一个观点:
animatePowBoomWhenHearNoiseInMicrophone
是纯函数,因为它对同一输入programA
和programB
的结果完全相同。你可以main = programA
或main = programB
,它会完全一样。
animatePowBoomWhenHearNoiseInMicrophone
是一个接收两个参数并导致程序描述的函数。如果您将main
设置为它,或者通过绑定将其包含在main
中,Haskell运行时可以执行此描述。
什么是IO
? IO
是用于描述命令式程序的DSL,以“pure-haskell”数据结构和函数编码。
“complete-haskell”又名GHC是“pure-haskell”的实现,是IO
解码器/执行器的必要实现。
答案 3 :(得分:8)
很简单归结为extensional equality:
如果您要拨打getLine
两次,那么两次通话都会返回一个IO String
,每次外部看起来都完全相同。如果您要编写一个函数来获取2 IO String
并返回Bool
来表示它们之间检测到的差异,则无法检测到任何可观察属性的任何差异。它无法询问任何其他功能是否相同,并且任何使用>>=
的尝试也必须返回IO
中的所有等于外部的内容。
答案 4 :(得分:4)
答案 5 :(得分:3)
即使它的标题有点奇怪(因为它与内容不完全匹配),下面的haskell-cafe线程包含了关于Haskell的不同IO模型的很好的讨论。
http://www.mail-archive.com/haskell-cafe@haskell.org/msg79613.html
答案 6 :(得分:2)
嗯,这就是我们在大学里教过的东西 -
当函数始终为指定的输入返回相同的值时(或同一表达式始终在同一上下文中计算为相同的值),该函数是引用透明的。因此,例如getChar
如果只有() -> Char
或Char
类型签名,则不会是引用透明的,因为如果使用相同的参数多次调用此函数,则可能会得到不同的结果。
但是,如果你引入了IO monad,那么getChar
可以有IO Char
类型,而且这种类型只有一个值 - IO Char
。因此,无论关键用户真正按下了什么,getChar
总是会重新获得相同的价值。
但是你仍然能够“获得”这个IO Char
事物的潜在价值。好吧,不是真的得到,而是使用绑定操作符(>>=
)传递给另一个函数,因此您可以使用用户在程序中进一步输入的字符。
答案 7 :(得分:1)
我无法理解M()如何使空参数列表()减少虚假,但是wadler非常清楚 Monads只是表示一种副作用,它们并没有消除它 。 Haskel的追随者似乎在说monad消除杂质时欺骗了我们和他们自己。
答案 8 :(得分:1)
单价
IO
的纯含义是什么?
从某种意义上说,IO
类型的值是Standard ML 抽象命令代码的一部分,理想情况下,这些代码只能由Haskell实现的RTS处理-在{ {3}},菲利普·沃德勒(Philip Wadler)提供了有关如何实现的提示:
(* page 26 *)
type 'a io = unit -> 'a
infix >>=
val >>= : 'a io * ('a -> 'b io) -> 'b io
fun m >>= k = fn () => let
val x = m ()
val y = k x ()
in
y
end
val return : 'a -> 'a io
fun return x = fn () => x
(* page 27 *)
val execute : unit io -> unit
fun execute m = m ()
但是,How to Declare an Imperative认为这种情况可以接受:
[...]一种机器上最无状态的无状态计算模型 突出的特征是状态[意味着]模型与机械之间的差距很大,因此弥合成本很高。 [...]
在适当的时候,功能的主角也意识到了这一点。 语言。他们以各种棘手的方式引入了状态(和变量)。 因此,纯粹的功能特征已被妥协和牺牲。 [...]
... Niklaus Wirth.(R)的所有人吗?
我已经将IO monad描述为状态monad,其中状态是“现实世界”。
这将是I / O的经典行星通过模型,Miranda直接使用该模型:
import StdFile
import StdMisc
import StdString
Start :: *World -> *World
Start w = putString "Hello, world!\n" w
putString :: String *World -> *World
putString str world
# (out, world1) = stdio world
# out1 = fwrites str out
# (_, world2) = fclose out1 world1
= world2
支持I / O的方法的拥护者认为,这使I / O操作变得纯净,如引用透明。为什么呢?
因为它通常是正确的。
来自标准的Haskell 2010库模块Clean:
mapAccumL _ s [] = (s, [])
mapAccumL f s (x:xs) = (s'',y:ys)
where (s', y ) = f s x
(s'',ys) = mapAccumL f s' xs
如果这个习惯用法如此普遍,以至于有特定的定义可以支持它,那么将其用作I / O模型(具有合适的状态类型)真的就不足为奇了。
来自最近的Data.List(“唯一世界”上的I / O ,第148页,第24页)
赋予初始表达式的世界是抽象数据结构,是类型为
*World
的抽象世界 从程序中看到具体的物理世界。抽象世界原则上可以包含 功能程序在与具体世界执行期间需要进行什么交互的任何东西。世界可以看作是 状态和对世界的修改可以通过在世界或世界的一部分上定义的状态转换函数来实现。通过 要求这些状态转换函数在 unique 世界上起作用,可以直接对抽象世界进行修改 可以在现实的物理世界中实现,而不会降低效率和参考透明性。
但是考虑这个小的Clean程序:
Start :: *World -> *World
Start w
# w1 = putString "Ha "
= loop w1
loop :: *World -> *World
loop w = loop w
如果putString
是纯净的,则w1
表示字符串"Ha "
被发送到Clean的标准I / O机制,因此如果w1
永远不会返回,那么"Ha "
根本就不会出现...
而且,难道无法像现实世界中的功能那样描述几乎所有非纯函数吗?
是; FFI在Haskell 2010中的工作原理差不多。
在我看来,单子
IO
类型中的代码似乎有很多可观察到的副作用。
如果您使用的是GHC,它不是外观-从 Clean Language Report(第55页的第26页),作者:保罗·休达克,约翰·休斯,西蒙·佩顿·琼斯和菲利普·沃德勒:
当然,GHC实际上并没有绕过整个世界;相反,它传递了一个虚拟的“令牌”,以确保在存在惰性评估的情况下正确地对动作进行排序,并且将输入和输出作为实际副作用执行!
但这只是实现细节:
IO
计算是一种函数(在逻辑上)获取世界的状态,并返回修改后的世界以及返回值。
逻辑不适用于现实世界。
Marvin Lee Minsky。