2013-02-10 85 views
3

我學習了命令式編程語言,主要是C++和C語言,所以功能方法對我來說是非常新的。當我在寫函數/方法之前,我通常採用「增量」方法(可能大多數人都這樣做):編寫一小部分代碼,然後檢查結果是否如預期的那樣(通常由只是用printf或std :: cout將它們打印到stdout中),改進算法,增強算法,然後檢查到目前爲止的結果是否如預期的那樣(通常只需將它們打印到標準輸出或標準輸出),精煉...我很少寫完整的方法。Haskell診斷輸出

基本這個「增量」的方法是有診斷輸出(printf的或std :: COUT在我的例子以上)的能力。但是在Haskell中(據我瞭解截至目前),如果我想 - 比如說 - 使用'putStrLn'將某些內容寫入標準輸出,我必須更改函數的簽名,因爲'putStrLn'只返回一個IO Monad包含我想要打印的信息,但不會在調用'putStrLn'時打印它,對嗎?所以每次我想使用'putStrLn'進行診斷輸出時,我都必須更改當前函數的簽名以及所有其他函數的調用方式等。

所以有一種便宜且簡單的方法將函數的「局部變量」的值打印到標準輸出中?

或者僅僅是這樣一個事實:我要求的是一個標誌,我不瞭解Haskell編程的基礎部分?

回答

8

這很奇怪,因爲我發現沒有使用過的語言的閱讀評估打印循環(REPE),我總是感到沮喪,因爲我一直在測試我的代碼有多少工作。 REPL是我增量代碼開發的基礎。您可以使用它來測試您的代碼而不需要必須添加一堆打印語句。

  • 讓GHCi與您的編輯器同時打開。
  • 編寫更小,單一用途的功能。這看起來似乎很滑稽,但函數應用程序是Haskell的基本工作單元,並沒有在命令式語言中獲得的那種開銷。
  • 每次您編寫函數時,都要在GHCi中執行:r並用各種輸入進行測試。
  • Haskell非常密集,因此屏幕上顯示的內容比您習慣的短得多。

偶爾你最終陷入漫長的單子計算或其他事情。 GHCi允許您設置斷點 - 優先使用這些代碼將打印語句添加到代碼中,因爲您可以在不編輯代碼的情況下進行更多調查和調查,最重要的是,您不需要在類型簽名中添加顯示約束。

完成後,您可以手動內聯免費短助手功能並使用ghc -O2進行編譯。

(使用手動添加打印語句,或者Debug.Trace模塊是在我的經驗相比,這是一個完整的疼痛。)

摘要:只要有可能,避免編輯您的代碼,而測試它。使用GHCi很多。

1

它能夠迅速地不純調試輸出加到使用Debug.Trace模塊的trace功能的純函數。它是一個函數,當第二個參數/返回值被強制時,返回第二個參數,並帶有打印第一個參數的附加副作用。

我認爲這是完全可以接受的,因爲它不以任何最終提交或其他永久性的​​代碼最終會暫時用這個調試爲長。此外,打印消息的順序與評估順序相匹配,對於調試也很有用,但並不總是輸出的首選順序。

如果您需要使用這個很多時候也可能是你需要係數代碼爲更小的功能,這使得它更容易被剛剛指定的參數來看,在返回值檢查自己的行爲的跡象。

13

沒有好的方法去做你想做的事。您可以通過Debug.Trace親近,但我不建議在學習時因爲Haskell的非標準評估順序。 Haskell不能像C和C++這樣的語言順序設置「變量」的值。因爲它是懶惰的,所以Haskell表達式按照依賴於使用的順序進行評估,所以增量值並不真正起作用。

Haskell是一個表達取向的語言。用它來你的優勢:

  1. 寫簡短的函數。通過這種方式可以更容易地看到每個函數的功能。大多數函數應該是每個方程一行,真正的「一行」應該是常見的。
  2. 使用REPL。您應該經常在GHCi中試驗您的代碼。使用類型系統。在大多數命令式語言中,Haskell的類型系統比類型系統更有用。以機器檢查的方式鍵入文檔意圖。如果不理解類型,你不能指望理解代碼。在編寫代碼時,一旦你獲得了正確的類型,你就完成了大部分工作。

結合上述建議。您可以通過:t獲得GHCi中表達式的類型。

+0

但並不意味着我將有很多功能,因此命名空間將滿的功能呢?因爲在函數中局部定義許多輔助函數使得它們無法從外部訪問,因此很難嘗試...另外,爲這些函數找到有意義的函數名將更加困難,並且我看到了Haskell中保留函數名稱的趨勢簡短...這一切都讓我困惑:)在C++中,例如,儘量保持「本地」和儘可能封裝... – Sh4pe 2013-02-10 11:51:57

+1

在Haskell中,我們使用模塊和導出列表進行封裝。爲函數尋找有意義的名字並不比通過一些練習找到局部變量的名字更困難。我認爲Haskell的趨勢就是保持經常使用的函數名稱簡短。我經常對模塊內部函數有很長的描述性名稱,這些名字只用於我目前正在使用的代碼中的一個例子'boundedValueBlockTraversal'。我也傾向於完整地記錄這些功能,這會產生非常易讀且易於維護的代碼。 – jix 2013-02-10 19:00:33

1

首先,您可以通過將它們加載到ghci並在那裏玩它們來調試您的功能。

然後,您可以使用的trace在計算表達式時打印字符串。但請注意,由於Haskell使用懶惰評估,在大多數情況下,表達式將在不同於預期的時間進行評估。另見WikibooksHaskell wiki。 (內部trace使用不安全的調用,允許它即使在純代碼的打印輸出。通常情況下,你不應該使用它們,但在這種特殊情況下,它是確定。)

+0

爲了更加明確,「這個特例」就是發展。 'Debug.Trace'在生產代碼中沒有地位。 – amindfv 2013-02-11 02:11:37

0

有怎樣一個相當短的例子here去構建一些與你描述的東西類似的東西。如果我正確地閱讀它,作者創建了一個簡單的monad,可以讓您在計算過程中打印出來,可以這麼說。

0

逼近:

  1. GHCI調試打印局部變量而不會干擾你的代碼的方式。

  2. WriterT monad變換器,無論是嚴格還是懶惰,都可以對日誌進行序列化,如果您返回跟蹤值並與結果配對。

{-# LANGUAGE PackageImports #-} 
-- import qualified "transformers" Control.Monad.Trans.Writer.Strict as W 
import qualified "transformers" Control.Monad.Trans.Writer.Lazy as W 

compute:: Int -> Int -> (Int, Int) 
compute x y = (result, local) 
    where 
    local = 2 * x 
    result = local + y 

test :: (Monad m) => W.WriterT String m Int 
test = do 
    let (r1, local1) = compute 5 3 
    W.tell $ "local1= " ++ show local1 ++ "\n" 

    let (r2, local2) = compute 2 2 
    W.tell $ "local2= " ++ show local2 ++ "\n" 

    return $ r1 + r2 

main = do 
    (r, logs) <- W.runWriterT test 
    putStrLn logs 
    putStrLn $ "result= " ++ show r 

輸出:

 
local1= 10 
local2= 4 

result= ...