【问题标题】:Record update failure in IO Monad?IO Monad 中的记录更新失败?
【发布时间】:2010-11-23 17:42:48
【问题描述】:

我希望你能帮助我。经过多年的命令式语言,我是 Haskell 菜鸟,所以如果我犯了一个愚蠢的错误,请解释一下,以便我学习。

我有以下数据类型:

data DicomSopInstance = DicomSopInstance {
sopInstancePath :: String,
sopInstanceUid :: String,
sopInstancePk :: Int64,
seriesFk :: Int64,
sopInstanceFrameCount :: Int32,
sourceDicom :: Maybe EncapDicomObject
}

我根据数据库查询的结果构造这种类型的实例。当结果出现在 sourceDicom 字段中时,不能有任何值,所以我将其设为 Maybe 值。当我尝试加载 EncapDicomObject 并使用结果更新数据类型时,问题就出现了,因此我不必每次想要访问它时都从磁盘加载 EncapDicomObject。

以下是导致问题的代码。我的目的是测试是否已从磁盘读取 EncapDicomObject,如果已加载,则使用现有的(Just)值,如果没有(未检测到),则加载它并将 Nothing 更改为 Just。麻烦的行标有“**”

showImage :: TextCtrl t -> DicomImage -> IO ()
showImage textCtl image = do
  let sopInst = sopInstance image
  let maybeEncapDicom = sourceDicom sopInst
  case maybeEncapDicom of
  Just encapDicom -> do
    showEncapDicomObject textCtl encapDicom (sopInstancePath sopInst)
    return ()
  Nothing         -> do
    eitherDicom <- readDicomFile $ sopInstancePath sopInst
    case eitherDicom of
      Left errorMessage -> do
        infoM "Hastur" $ "Error reading DICOM file: " ++
          (sopInstancePath sopInst) ++ " - " ++ errorMessage
        textCtrlSetValue textCtl $ "*** DICOM: " ++
          (sopInstancePath sopInst) ++ " ***\n"
        textCtrlAppendText textCtl errorMessage
        textCtrlAppendText textCtl "\n*** [End] ***"
        textCtrlShowPosition textCtl 0
        return ()
      Right encapDicom  -> do
      sopInst { sourceDicom = Just encapDicom } -- ****
        showEncapDicomObject textCtl encapDicom (sopInstancePath sopInst)
        return ()

如果我注释掉标记的行,那么代码会编译,但它每次都会加载文件,因为它总是遇到 Nothing。如果我取消注释,我会收到以下错误:

src\Hastur.hs:382:10:
    Couldn't match expected type `IO a'
           against inferred type `DicomSopInstance'
    In a stmt of a 'do' expression:<br>
        sopInst {sourceDicom = Just encapDicom}

我将此解释为意味着 stmt 返回一个 DicomSopInstance 而不是 IO () 但我所有尝试创建一个函数来更新 sopInst 并返回 IO () 的尝试都失败了。

我错过了什么?当 Haskell 的非严格行为会为我这样做时,我是在尝试按需加载,还是我只是设计错误?我将 sourceDicom 转换为可变变量的尝试也失败了:(

干杯

詹姆斯

【问题讨论】:

  • 以后请将您的代码缩进四个空格或使用代码按钮(带有 1 和 0 的那个),以便您的代码格式正确。
  • Ack :( AdBlock 对我隐藏了很多界面。我希望已修复。

标签: haskell io record monads


【解决方案1】:

您还不太了解函数式范例。 sopInst 在函数顶部定义。它内部没有可变引用——它的值是一成不变的。您以后无法更改该值。相反,您可以为另一个事物指定一个名称,它是原始事物的更改版本。例如,尝试以下操作。

Right encapDicom  -> do
  let newSopInst = sopInst { sourceDicom = Just encapDicom }
  showEncapDicomObject textCtl encapDicom (sopInstancePath newSopInst)
  return ()

请注意,由于事物是不可变的,因此需要进行大量共享。假设您的SopInst 类型是C 中的一条记录。从概念上讲,它具有指向其所有成员的指针。当您构造newSopInst 时,您只需获得该指针记录的副本,其中一个指针现在指向sourceDicom 的新值——其他字段指向的值是共享的。这意味着这种编程风格(以更多的间接为代价——无论如何都是懒惰所必需的)效率比你可能担心的要低得多,而且作为奖励,如果你在其他地方需要它,你仍然可以使用旧的sopInst . (如果你不这样做,当然,它会被垃圾收集)。

【讨论】:

  • 感谢您的回复。我认为您在一般意义上可能是对的,我仍在学习。当我编写代码时,我明白我将替换 sourceDicom,但可能并没有完全替换 sopInst。我很乐意更换一个小结构并收集旧结构。
  • sigh 仍然掌握这个窍门 也许我应该问的是:“我需要在加载文件时更新这些对象,我该怎么做?”我的 Haskell 是有限的,但我意识到更新值会覆盖它,但这是我的意图。我只是想弄清楚如何保留更改
  • 哦!我懂了!您想更新您通过的 DicomImage!不要那样做!只需return (image {sopInstance = newSopInst}) 并为您的函数提供showImage :: TextCtrl t -&gt; DicomImage -&gt; IO DicomImage 的类型。您总是可以作弊并使用真正的可变状态与 MVar/IORef,但更好的是尽早掌握“正确”方式的窍门,并且仅在真正必要时使用 MVars/IORefs(通常,使用并发或某些有趣的类型ffi)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-24
  • 1970-01-01
  • 2020-12-06
  • 1970-01-01
  • 2016-03-03
  • 1970-01-01
  • 2019-04-02
相关资源
最近更新 更多