记录IO Monad中的更新失败?

时间:2010-11-23 17:42:48

标签: haskell io record monads

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

我有以下数据类型:

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 :(得分:4)

你不太了解功能范例。 sopInst定义在函数的顶部。它里面没有可变引用 - 它的值是固定的。您以后无法更改该值。相反,您可以为其他事物指定名称,这是原始名称的更改版本。例如,请尝试以下操作。

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

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