haskell - IO Monad 记录更新失败?

标签 haskell io record monads

希望你能帮助我。经过多年的命令式语言学习,我是一个 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 转换为可变变量也没有成功:(

干杯

詹姆斯

最佳答案

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

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

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

关于haskell - IO Monad 记录更新失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4259079/

相关文章:

haskell - 为什么该实例不符合覆盖条件?

haskell - 这个 Haskell 代码会占用太多内存吗?

c - 为什么 PgUp(^[[5~) 或 PgDn(^[[6~) 输入在从 getchar 获取时被认为是 4 个字符而不是 5 个字符?

Java——适合字符缓冲区的大小

ios - 如何录制音频流以将其保存在 file/swift 4.2 中

haskell - 对受约束的 GADT 记录使用记录更新语法

ruby-on-rails - 创建记录时出错 : nil object instead of array?

haskell - 无法将类类型与 haskell 中的特定类型匹配

haskell - 非柯里化(Currying)函数

java - 为什么我的程序停止运行并且不返回错误?