关于Haskell的一元IO构造:
IO-monad的事情?IO单片,然后以一种特殊的方式处理它(或其他什么)?真正的原因是什么--最终你最终使用了副作用,那么为什么不用简单的方法呢?
发布于 2017-07-17 05:41:14
你能不能直接跳到libc.so中去做IO和跳过instead这件事?
从函数中获得,如果您将FFI函数声明为纯函数(所以,不引用IO),那么
GHC认为计算纯函数结果的两倍是没有意义的。
这意味着函数调用的结果被有效地缓存。例如,一个程序,其中一个外来的不纯伪随机数生成器被声明返回一个CUInt。
{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign
import Foreign.C.Types
foreign import ccall unsafe "stdlib.h rand"
c_rand :: CUInt
main = putStrLn (show c_rand) >> putStrLn (show c_rand)每次调用(至少在我的编译器/系统上)返回相同的内容:
16807
16807如果我们将声明更改为返回IO CUInt
{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign
import Foreign.C.Types
foreign import ccall unsafe "stdlib.h rand"
c_rand :: IO CUInt
main = c_rand >>= putStrLn . show >> c_rand >>= putStrLn . show然后,这会导致(可能)每个调用返回一个不同的数字,因为编译器知道这是不正确的:
16807
282475249因此,您将不得不在调用标准库时使用IO。
发布于 2017-07-17 10:14:55
假设使用FFI,我们定义了一个函数
c_write :: String -> ()这取决于它的纯度,因为每当它的结果被强制的时候,它就会打印字符串。为了避免遇到Michal的答案中的缓存问题,我们可以定义这些函数以获得一个额外的()参数。
c_write :: String -> () -> ()
c_rand :: () -> CUInt在实现层面上,只要CSE不太激进(这在GHC中是不存在的,因为这可能会导致意外的内存泄漏,结果就是如此),这是可行的。既然我们已经有了这样的定义,Alexis指出了许多令人尴尬的用法问题--但是我们可以用一个单子来解决它们:
newtype IO a = IO { runIO :: () -> a }
instance Monad IO where
return = IO . const
m >>= f = IO $ \() -> let x = runIO m () in x `seq` f x
rand :: IO CUInt
rand = IO c_rand基本上,我们只是把Alexis所有的尴尬用法问题都塞进了一个单播,只要我们使用一元界面,一切都是可以预测的。从这个意义上说,IO只是一种约定--因为我们可以在Haskell中实现它,没有什么基本的东西。
那是从作战的有利位置。
另一方面,Haskell在报告中的语义是单独使用https://en.wikibooks.org/wiki/Haskell/Denotational_semantics指定的。在我看来,Haskell具有精确的表示语义这一事实是该语言最美丽和最有用的特性之一,它使我有了一个精确的框架来思考抽象,从而精确地管理复杂性。虽然通常的抽象IO单体没有公认的表示语义(让我们中的一些人哀叹),但至少可以想象我们可以为它创建一个表示模型,从而保留Haskell表示模型的一些优点。然而,我们刚才给出的I/O形式与Haskell的表示语义完全不相容。
简单地说,只有两个类型为()的可区分值(模块化致命错误消息):()和⊥。如果我们将FFI作为I/O的基础,并且只使用IO monad“作为一种约定”,那么我们有效地为每种类型添加了一个in值--为了继续具有一个表示语义,每个值都必须与在其评估之前执行I/O的可能性相结合,并且由于这带来的额外复杂性,我们基本上失去了考虑任何两个不同的程序的能力,除非是在最简单的情况下--也就是说,我们失去了重构的能力。
当然,由于unsafePerformIO在技术上已经是这样,高级Haskell程序员也需要考虑操作语义。但是大多数情况下,包括在使用I/O时,我们可以忘记所有这些,并且充满信心地重构,这正是因为我们已经了解到,当我们使用unsafePerformIO时,我们必须非常小心地确保它运行良好,它仍然为我们提供了尽可能多的表示推理。如果一个函数有unsafePerformIO,我会自动给予它比普通函数多5到10倍的注意,因为我需要理解有效的使用模式(通常类型签名告诉我我需要知道的一切),我需要考虑缓存和竞争条件,我需要考虑我需要强迫它的结果有多深,等等。它是awful1。FFI I/O也需要同样的注意。
总结:是的,这是一个惯例,但是如果你不遵守它,我们就不能拥有美好的东西。
实际上,我认为这很有趣,但是一直考虑所有这些复杂的问题是不现实的。
发布于 2017-07-17 19:50:06
这取决于“是”的含义-或者至少是“公约”的含义。
如果“约定”意味着“事情通常的方式”或“各方之间就某一特定问题达成的协议”,那么很容易给出一个乏味的答案:是的,IO单体是一种惯例。这是语言的设计者同意处理IO操作的方式,也是语言的用户通常执行IO操作的方式。
如果我们被允许选择一个更有趣的定义“约定”,那么我们可以得到一个更有趣的答案。如果“约定”是用户为了在没有语言本身的帮助下实现特定目标而强加于语言的一种纪律,那么答案是否定的:IO monad是与约定的相反的。它是由语言(语言)强制执行的一门学科,它帮助用户构建程序并进行程序推理。
IO类型的目的是明确区分“纯”值的类型和需要运行时系统执行以生成有意义结果的值类型。Haskell类型系统强制执行这种严格的分离,防止用户(例如)创建Int类型的值来发射众所周知的导弹。这不是第二个意义上的惯例:它的整个目标是将以安全和一致的方式执行副作用所需的纪律从用户转移到语言及其编译器上。
你能不能直接跳到libc.so中去做IO和跳过instead这件事?
当然,在没有IO单点的情况下可以执行IO :查看几乎所有现存的编程语言。
它会起作用吗?或者结果是不确定的,因为Haskell评估懒惰或其他什么的,比如GHC是针对IO Monad的模式匹配,然后以一种特殊的方式或其他方式处理它。
没有免费午餐这样的东西。如果Haskell允许任何值要求执行涉及IO的操作,那么它就必须失去我们所看重的其他东西。其中最重要的可能是引用透明度:如果myInt有时是1,有时是5 (取决于外部因素),那么我们将失去以严格的方式(称为等式推理)对程序进行推理的大部分能力。
在其他答案中提到了懒惰,但懒惰的问题是共享不再安全。如果x在let x = someExpensiveComputationOf y in x * x中不是引用透明的,则GHC将无法共享该工作,并且必须计算它两次。
真正的原因是什么?
如果不严格分离有效值和由IO提供并由编译器强制执行的无效值,Haskell实际上将不再是Haskell。有许多语言不执行这一纪律。最好至少有一个这样的人。
到头来,你最终还是有副作用的。那么为什么不用简单的方式去做呢?
是的,最后,您的程序由一个名为main的值表示,该值具有IO类型。但问题不是你在哪里结束,而是你从哪里开始的,,:如果你能以严格的方式区分有效和无效的价值,那么你在构建这个程序时就获得了很多优势。
https://stackoverflow.com/questions/45136398
复制相似问题