希望你能帮助我。经过多年的命令式语言学习,我是一个 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/