2012-03-05 27 views
5

這是一個挑戰問題,而不是一個有用的問題(我花了幾個小時)。鑑於一些功能,如何在Haskell中編寫一系列printf函數(調試打印等)

put_debug, put_err :: String -> IO() 
put_foo :: String -> StateT [String] m() 

我想寫一個廣義的printf函數,調用它GPRINT,這樣我可以寫

pdebug = gprint put_debug 
perr = gprint put_err 
pfoo = gprint put_foo 

然後用pdebugperrpfooprintf,例如,

pdebug "Hi" 
pdebug "my value: %d" 1 
pdebug "two values: %d, %d" 1 2 

我無法想出一個足夠一般的班級。我嘗試過的事情一樣(對於熟悉Printf,或Oleg的可變參數函數的方法)

class PrintfTyp r where 
    type AppendArg r a :: * 
    spr :: (String -> a) -> String -> [UPrintf] -> AppendArg r a 

class PrintfTyp r where 
    type KRetTyp r :: * 
    spr :: (String -> KRetTyp r) -> String -> [UPrintf] -> r 

兩者都太難寫基地的實例爲:有沒有好的選擇。r對於第一種方法(並且,其類型不反映在非內射索引類型家族AppendArg中),並且在第二種方法中,最終寫入instance PrintfTyp a,其看起來不正確(匹配太多類型)。

同樣,這只是一個挑戰問題:只有在它很有趣的時候纔會這樣做。儘管如此,我一定會好奇地知道答案。謝謝!!

回答

3

下面是試圖讓現有Text.Printf做盡可能多的工作儘可能的一種方法。首先,我們需要一些擴展:

{-# LANGUAGE TypeFamilies #-} 
{-# LANGUAGE FlexibleContexts #-} 

-- To avoid having to write some type signatures. 
{-# LANGUAGE NoMonomorphismRestriction #-} 
{-# LANGUAGE ExtendedDefaultRules #-} 

import Control.Monad.State 
import Text.Printf 

的想法是一次喂一個參數爲printf得到格式化String,然後採取和它給我們在給予的行動開始。

gprint :: GPrintType a => (String -> EndResult a) -> String -> a 
gprint f s = gprint' f (printf s) 

class PrintfType (Printf a) => GPrintType a where 
    type Printf a :: * 
    type EndResult a :: * 
    gprint' :: (String -> EndResult a) -> Printf a -> a 

的遞歸步驟需要一個參數,並將其輸送到printf呼叫我們在g建立。

instance (PrintfArg a, GPrintType b) => GPrintType (a -> b) where 
    type Printf (a -> b) = a -> Printf b 
    type EndResult (a -> b) = EndResult b 
    gprint' f g x = gprint' f (g x) 

基礎的情況下只給結果字符串爲f

instance GPrintType (IO a) where 
    type Printf (IO a) = String 
    type EndResult (IO a) = IO a 
    gprint' f x = f x 

instance GPrintType (StateT s m a) where 
    type Printf (StateT s m a) = String 
    type EndResult (StateT s m a) = StateT s m a 
    gprint' f x = f x 

這裏的測試程序我使用:

put_debug, put_err :: String -> IO() 
put_foo :: Monad m => String -> StateT [String] m() 

put_debug = putStrLn . ("DEBUG: " ++) 
put_err = putStrLn . ("ERR: " ++) 
put_foo x = modify (++ [x]) 

pdebug = gprint put_debug 
perr = gprint put_err 
pfoo = gprint put_foo 

main = do 
    pdebug "Hi" 
    pdebug "my value: %d" 1 
    pdebug "two values: %d, %d" 1 2 
    perr "ouch" 
    execStateT (pfoo "one value: %d" 42) [] >>= print 

和輸出:

DEBUG: Hi 
DEBUG: my value: 1 
DEBUG: two values: 1, 2 
ERR: ouch 
["one value: 42"] 
0

我不確定編譯器將能夠推斷出這一點。它如何知道你期望字符串在StateT monad的上下文中被打印,而不是在(a ->) monad中採用另一個參數。

您可能需要介紹一種方式來顯示參數列表結束時的類型檢查器。最簡單的方法是隻把它包在一個函數,所以你寫:

pdebug $ printf "%d %d %d" 1 2 3 

然後pdebug可能在單子多態。

您可能也有能擺動它讓你用一個終止符,如:

data Kthx = Kthx 
printf "%d %d %d" 1 2 3 Kthx 

但我不能完全弄清楚如何現在。

+0

呀,我想避免終結者。我更感興趣的是隻支持一個參數,即不支持'pdebug「無參數」''的情況。不過謝謝。 – gatoatigrado 2012-03-05 18:46:56

1

類用於基於類型的調度。因此,對於put_fooText.Printf體系結構已經令人滿意(儘管它不會出口PrintfType,遺憾)。例如,下面似乎運作良好:

{-# LANGUAGE TypeFamilies #-} -- for ~ syntax 
import Control.Monad.State 
import Data.Default 

-- copy and paste source of Text.Printf here 

put_foo :: String -> StateT [String] m() 
put_foo = undefined 

instance (Default a, Monad m, s ~ [String]) => PrintfType (StateT s m a) where 
    spr s us = put_foo (spr s us) >> return def 

put_debugput_err,你可以以同樣的方式HPrintfType確實概括PrintfType,而是採取了String -> IO()函數,而不是一個手柄。然後你會寫

pdebug = funPrintf put_debug 
perr = funPrintf put_err 
printf' = funPrintf putStr -- just for fun 
pfoo = printf