IO Monad 记录更新失败?

发布于 2024-10-04 04:36:20 字数 2166 浏览 9 评论 0原文

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

欢呼

James

I hope you can help me. I am a Haskell noob after years of imperative languages so if I'm making a stupid mistake, please explain it so I can learn.

I have the following data type:

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

I construct instances of this type from the results of a database query. When the results come in the sourceDicom field cannot have any value so I made it a Maybe value. The problem comes when I try to load the EncapDicomObject and update the data type with the result so I don't have to load the EncapDicomObject from disk every single time I want to access it.

The following is the code that is causing the problem. My intent is to test whether the EncapDicomObject has been read from disk, if it has been loaded then use the existing (Just) value, if not (Nothing is detected) then load it and change Nothing to Just. The troublesome line is marked with "**"

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 ()

If I comment out the marked line then the code compiles but it loads the file every time since it always encounters Nothing. If I uncomment I get the following error:

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}

I interpret this as meaning that the stmt returns a DicomSopInstance instead of IO () but all my attempts to create a function to update the sopInst and return IO () have failed.

What am I missing? Am I trying to do an on-demand load when Haskell's non-strict behaviour would do that for me or have I simply got the wrong design? My attempts to convert sourceDicom to a mutable variable have come to nought as well :(

cheers

James

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

彼岸花ソ最美的依靠 2024-10-11 04:36:20

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

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

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

You're not quite understanding the functional paradigm. sopInst is defined at the top of your function. It doesn't have mutable references inside -- its value is set in stone. You can't change that value later. Instead, you can assign a name to another thing, which is a changed version of the original one. Try the following, for example.

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

Note that since things are immutable, there's a great deal of sharing going on. Imagine that your SopInst type is a record in C. Conceptually, it has pointers to all its members. When you construct the newSopInst then you just get a copy of that record of pointers, with one pointer now pointing to a new value for sourceDicom -- the values pointed to by the other fields are shared. This means that this style of programming (at the cost of more indirections -- necessitated by laziness anyway) is much less inefficient than you might fear, and as a bonus, you still have the old sopInst hanging around should you need it elsewhere. (If you don't, of course, it will get garbage collected).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文