一种避免常见使用unsafePerformIO的方法

fuz*_*fuz 16 haskell global-variables purely-functional unsafe-perform-io

我经常在Haskell代码中找到这种模式:

options :: MVar OptionRecord
options = unsafePerformIO $ newEmptyMVar

...

doSomething :: Foo -> Bar
doSomething = unsafePerformIO $ do
  opt <- readMVar options
  doSomething' where ...
Run Code Online (Sandbox Code Playgroud)

基本上,一个人有一个选项或类似的记录,最初是在程序开始时设置的.由于程序员很懒惰,他不想options在整个程序中携带记录.他定义了一个MVar保持它 - 由丑陋的使用定义unsafePerformIO.程序员确保状态只设置一次,并且在任何操作发生之前.现在程序的每个部分都必须unsafePerformIO再次使用,只是为了提取选项.

在我看来,这样的变量被认为是实用的纯粹(不要打败我).是否有一个库抽象出这个概念,并确保变量只设置一次,即在初始化之前没有调用,并且不需要写unsafeFireZeMissilesAndMakeYourCodeUglyAnd DisgustingBecauseOfThisLongFunctionName

scl*_*clv 20

为了一点暂时的便利而交易必要的参考透明度的人不应该既不纯洁也不方便.

这是一个坏主意.您找到的代码是错误的代码.*

没有办法安全地完全包裹这个模式,因为它不是一个安全的模式.不要在代码中执行此操作.不要寻找一种安全的方法来做到这一点.没有一种安全的方法可以做到这一点.把unsafePerformIO地板上下来,慢慢地,并返回从控制台而去...

*人们确实使用顶级MV的合理原因,但这些原因与大多数情况下绑定到外部代码或其他一些非常混乱的东西有关.在这些情况下,据我所知,顶级MVar 不会从后面访问unsafePerformIO.

  • 所以JHC的代码很糟糕.GHC在很久以前就已经有了很多,而且他们已经慢慢地转变为纯粹的方式.这是唯一合理的方式. (7认同)
  • @Ganesh:您无法强制在访问ref之前运行初始化程序.如果你在另一方面穿过记录(通过隐式参数或直接),那么这可以作为各种各样的"证明义务".最重要的是,顶级IORef还存在其他常见问题. (2认同)

Mas*_*sse 9

如果您使用MVar进行设置或类似的设置,为什么不尝试阅读器monad?

foo :: ReaderT OptionRecord IO ()
foo = do
    options <- ask
    fireMissiles

main = runReaderT foo (OptionRecord "foo")
Run Code Online (Sandbox Code Playgroud)

(如果你不需要IO,请定期阅读:P)

  • @FUZxxl:只有实际需要这些选项的函数才会有一个额外的层,并且隐藏某些函数依赖于外部数据的事实不是"务实的",它是草率的.这与用不纯的语言抛出全局变量没什么不同. (22认同)
  • 有这样的信息的想法,必须是程序中的每个点都可以访问的谬误.我们有许多可用于分解这种依赖性的工具 - 基本上不依赖于这些数据的代码可以被赋予一种不能(准确地反映出来)的类型,并且可以在范围内最小化代码.试着把你的程序想象成不是成千上万行规范的伟大野兽,而是词汇量的定义,不超过几百行规范. (6认同)
  • @FUZxxl:为了对错误消息做任何事情,你必须在某个时候进入"IO".因此,不要使用字符串来表示错误,而是使用`type TranslatableError = OptionRecord - > IO String`,然后在显示消息之前将其应用于选项.只是几条额外的线,而不是成堆的样板. (4认同)
  • @FUZxxl:更不用说如果一个函数可以合理地遇到错误迫使它放弃并让一个调用者的错误处理程序处理它,你应该处于某种错误monad中,在这种情况下添加一个`ReaderT`没什么大不了的; 这就是许多应用程序定义自己的monad变换器堆栈的原因.如果您正在使用实际异常并在"IO"中捕获它们,那么,再次,您没有理由不能使用`unsafePerformIO`来获取选项.你在这里试图解决的问题实际上并不存在. (4认同)
  • 你错过了这一点.务实的程序员希望避免在每个函数上添加额外的层. (2认同)

Rob*_*een 5

使用隐式参数.它们的重量级略低于制作每种功能ReaderReaderT类型的重量级.您必须更改函数的类型签名,但我认为这样的更改可以编写脚本.(将为Haskell IDE提供一个很好的功能.)

  • @Bryan:[GHC用户指南涵盖了它](http://www.haskell.org/ghc/docs/latest/html/users_guide/other-type-extensions.html#implicit-parameters),足以得到至少是这个想法. (3认同)