【问题标题】:Is there a way to place some impure code inside pure functions?有没有办法在纯函数中放置一些不纯的代码?
【发布时间】:2017-05-22 04:58:32
【问题描述】:

IO,就像Maybe,只是Monad 的一个实例。另一方面,我们拥有MaybeJustNothing)的所有数据构造函数,但没有IO 的构造函数。 ReaderWriter 也不导出构造函数,它们具有返回此类型实例的函数(readerwriter),更重要的是 runReaderrunWriter,它们从 Monad 解包计算结果。

有没有办法解开 IO Monad?我想有一个纯函数,它在引擎盖下做一些不纯的 IO 计算。目前我可以用大多数 Monads 做到这一点

我知道这种棘手功能的一个例子:Debug.Trace.trace

【问题讨论】:

  • 本地不纯操作可以使用ST
  • 这正是 IO monad 试图阻止的。所有不纯的效果都归入 IO 内部。你到底为什么要打破它?
  • 这绝对不意味着您可以打开某些东西,因为它是Monad。考虑data Proxy a = Proxy(请注意,根本没有“包含在”Proxy 中的值)这是一个Monad
  • 你不能按设计解开IO,因为它的目的是标记必须由编译器实现的操作,而不是在Haskell代码中。
  • ST 是他要找的东西太仓促了。虽然它是解决许多问题的好方法,但他还没有说明需要,他的一个例子是trace - 当然不是ST 之类的任务。 @yanpas 您能否更具体地说明您的实际目标?

标签: haskell monads


【解决方案1】:

unsafePerformIO :: IO a -> a in System.IO.Unsafe(基础)。

谨慎使用,仔细阅读documentation中的描述。

【讨论】:

  • “谨慎使用”——这太弱了。正确的建议是不要在一般的 Haskell 代码中使用它,仅用于外部函数调用的包装器(即可以包装成适当纯的东西的 C 函数,这C语言本身不能保证)。 有时在 Haskell 本身中使用 unsafePerformIO 作为最后的优化也是有意义的,但这几乎没有必要(ST 可以完成您可能需要的大部分操作这)。你可以在 Haskell 中编写的任何函数也可以完全不使用 IO 编写,最坏的情况是它会更慢。
  • @leftaroundabout 也许你可能想为文档提交补丁,因为文档没有说。
  • @leftaroundabout,unsafePerformIO 有一些相当有趣的用例,我会很难过失去这些用例。在我的脑海中:1.运行(概念上纯粹的)并行计算。 2. 围绕惰性泛化设计的数据结构,其中结构中的节点被语义等效但“改进”的版本替换。有关此技术的讨论,请参阅 Kaplan、Okasaki 和 Tarjan 的“简单融合持久链表”(特别是第 2 节)。
【解决方案2】:

正确答案是

不,你不能!

嗯,是的,GHC 有一个叫做unsafePerformIO 的东西,但这不是 Haskell 标准的一部分,只是允许某些“来自其他语言的“道德上纯”的函数,将使用外部函数接口调用,并将这些函数的类型反映为如果你直接用纯 Haskell 编写它们会具有的类型。

请注意,“解开 IO monad”不会简单地为您提供该计算的结果。如果IO 是公共构造函数类型,它实际上(在概念上)看起来类似于以下内容:

data IO' a =
    WriteToFile FilePath String
  | PutStr String
  | WithStdLine (String -> IO' a)
  | ...
  | SequenceIO (IO' ()) (IO' a)

这种IO' a 值的模式匹配通常不会让您访问a 类型的任何内容,它只会为您提供一些要执行的操作的描述,也许还有一些功能这可能会产生一个a来自从环境中获得的中间结果。

真正完成有用工作的唯一方法仍然是现在这样:通过将其绑定到类似main 操作的东西,然后由某个“现实世界实体”(运行时)执行。

如果您想实现一个描述适当数学(即纯)函数但似乎适合带有​​突变等命令式编程风格的算法,那么您根本不应该在 IO monad 中实现它。您可能只需选择合适的数据结构就可以在普通的纯 Haskell98 中实现它,或者使用the ST monad 来实现例如数组更新具有与命令式语言相同的性能。

【讨论】:

    【解决方案3】:

    有没有办法在纯函数中放置一些不纯的代码?

    • 如果有方法可以做到这一点呢?

    • 如果其他人可以使用它会怎样?

    您是否愿意坐下来仔细检查您用来检查它们是否安全的每个库和程序的来源?

    如果你是,那么 Haskell 可能不适合你 - 我建议你看看像 Standard MLOCaml 这样的语言...


    你还在吗?

    好吧,您可以使用另一种方法:

    • 使用普通的普通函数从以效果为中心的代码中抽象出常规的 Haskell 代码。

    这样做是可能的,因为 函数是 Haskell 中的一等值 - 特别是,函数可以用作参数 例如:

    fmap  :: Functor f => (a -> b) -> f a -> f b
    liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
    

    随着抽象能力的提高,您将越来越多的工作委派给常规 Haskell 代码(以函数的形式),只有一小部分定义受到影响(包括 main :: IO ())。开始需要一些额外的努力,但长期的回报是可观的......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-17
      • 1970-01-01
      • 1970-01-01
      • 2021-06-21
      • 1970-01-01
      • 2021-12-21
      相关资源
      最近更新 更多