【问题标题】:Side Effects inside instance declaration实例声明中的副作用
【发布时间】:2019-09-18 06:28:18
【问题描述】:

假设我想为UTCTime 创建一个包装器:

data CustomDateStamp = CustomDateStamp
    { 
      stampValue :: UTCTime
    } deriving (Show, Eq, Ord, Typeable)

现在说我想为“现在”构建一个默认值,例如

instance Default CustomDateStamp where
    def = CustomDateStamp getCurrentTime def

这(显然)失败了:

   • Couldn't match expected type ‘UTCTime’
                  with actual type ‘IO UTCTime’
    • In the first argument of ‘CustomDateStamp’, namely ‘getCurrentTime’
      In the expression: CustomDateStamp getCurrentTime def
      In an equation for ‘def’: def = CustomDateStamp getCurrentTime def
    |
98  |     def = CustomDateStamp getCurrentTime def
    |                   ^^^^^^^^^^^^^^

我的问题是,如何在实例定义中使用副作用操作?这甚至可能吗?处理这种情况的首选方法是什么?

【问题讨论】:

  • 这不是一个好主意。在惰性语言中,很难预测表达式何时被评估,但另一方面,我们并不在意,因为纯度确保最终结果是相同的。您的实例破坏了此属性:let t=def in (t,t) 不再等同于 (def, def),因为后者可以评估为具有不同组件的对。更务实的是,def 可能仅在打印到屏幕时而不是在创建时进行评估,从而导致非常令人费解的时间戳。

标签: haskell types default monads side-effects


【解决方案1】:

第一个近似值:你不能那样做。一旦进入 IO,总是在 IO 中(并且已经有一个 Default 实例为 IO a 不做你想要的)。制定一个不同的计划。

【讨论】:

  • 从技术上讲,您可以使用 usafe-IO 做到这一点,但这不会像您期望的那样工作。
  • 是的,有第二个近似值与第一个不同。但我的回答和建议是有效的。
  • 您认为从设计的角度来看,提供的Default (IO a) 实例“应该”存在吗?我想它有时会提供便利,但代价是阻止默认(IO-)环境实例?
  • @moonGoose IO 和函数实例很好地协同工作:接受回调的库,比如KeyEvent -> IO (),可以很容易地被赋予def,这是一个合理的默认值(基本上,忽略事件和没做什么)。我认为我可以排除执行实际 IO 的实例;尽管比大多数 Haskeller 更喜欢 Default,因此可能比大多数人更喜欢使用它,但我并没有因为排除令人兴奋的 IO 实例而感到任何痛苦。
【解决方案2】:

我认为您可以为默认值需要 IO 操作的类型编写此代码,

instance {-# OVERLAPPING #-} Default (IO CustomDateStamp) where
    def = CustomDateStamp <$> getCurrentTime

(例如 mtl 堆栈可轻松调整)。有点争议,因为重叠很顽皮。

编辑:需要 OVERLAPPINGIO CustomDateStampIO a 更具体,因此它应该在范围内选择此实例。

【讨论】:

  • 我同意这在重叠实例是合理的假设世界中是合理的。然而,我们并不生活在那个世界里。
【解决方案3】:

由于getCurrentTime :: IO UTCTime,您不能直接调用它。没有IO a -&gt; a类型的函数

unsafePerformIO 除外(以及其他类似的魔法)。我强烈劝阻你不要走那条路。

【讨论】:

  • 这仍然不会像 OP 期望的那样工作。根据原因,有时是现在的实际情况,有时是过去的某个时间点。
  • 这是因为它没有任何有意义的工作方式,但它会以第一个近似值工作。因为编译器不缓存函数调用结果。
  • @talex,函数调用结果不会被缓存,但在该上下文中这不被视为函数调用。如果没有优化,它将使用函数调用(应用字典参数)来实现,因此可能不会被缓存。通过优化,专家几乎肯定会将调用提升到顶层,并且仅在第一次请求时间戳时运行IO 操作。
  • 通常的建议适用。初学 Haskell 程序员不应该使用任何不安全的IODebug.Trace 中的内容除外)。高级 Haskell 程序员知道他们几乎不应该使用 unsafe IO。在极少数情况下,高级程序员知道他们可以安全地使用它(通常与 FFI 结合使用)。在其余部分,他们会非常小心,并且很可能会发布一些微妙的错误代码,直到有人绊倒它。是的,这包括编写 GHC 运行时系统的高级 Haskell 程序员;这东西很难做对。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 2012-03-10
  • 1970-01-01
  • 2012-11-28
相关资源
最近更新 更多