2012-03-05 63 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