2010-11-23 93 views
2

我希望你能幫助我。我是一個Haskell noob,經過多年的命令式語言,所以如果我犯了一個愚蠢的錯誤,請解釋它,以便我可以學習。IO Monad中的記錄更新失敗?

我有以下數據類型:

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

如果我註釋掉標記線,則代碼編譯,但它每次加載,因爲它總是遇到沒有過的文件。如果我取消我收到以下錯誤:

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} 

我將此解釋爲意味着該語句返回DicomSopInstance而不是IO(),但我所有的嘗試創建一個函數來更新sopInst和返回IO()有失敗。

我錯過了什麼?當Haskell的非嚴格行爲會爲我做或者我只是得到錯誤的設計時,我是否試圖按需加載?我嘗試sourceDicom轉換爲可變變量都前功盡棄以及:(

歡呼

詹姆斯

+0

將來,請將您的代碼縮進四個空格或使用代碼按鈕(帶有「1」和「0」的代碼),以便您的代碼格式正確。 – sepp2k 2010-11-23 17:56:44

+0

Ack :(AdBlock隱藏了我的很多界面,修復了我希望的問題 – 2010-11-23 18:15:59

回答

4

你不太瞭解的功能範例。sopInst在你的函數的頂部定義。它裏面沒有可變引用 - 它的值被設置爲石頭,你不能在後面改變這個值,相反,你可以給另一個東西指定一個名字,這是另一個東西的名字。例如,以下例子:

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

請注意,由於事情是不可改變的,因此有很多共享正在進行。假設您的SopInst類型是C中的記錄。從概念上講,它有指向其所有成員的指針。當您構建newSopInst時,您只需獲取該指針記錄的副本,其中一個指針現在指向sourceDicom的新值 - 其他字段指向的值將被共享。這意味着這種編程風格(以更多的間接性爲代價 - 無論如何都是懶惰所必需的)的效率遠低於您可能會擔心的,並且作爲獎勵,如果您在其他地方需要它,您仍舊會掛着舊的編程。 (如果你不這樣做,當然會收到垃圾)。