2012-03-05 12 views
5

これは便利な問題以上の難題です(私は数時間を費やしました)。いくつかの機能、私は一般printf関数を書きたいHaskellでprintf関数のファミリを書く方法(デバッグプリントなど)

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

を考えると、私は

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

を書き、その後、例えば、printfようpdebugperr、およびpfooを使用することができるように、それはgprint呼び出します、

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

私は十分に一般的なクラスを考え出すことができません。私の試みは(Printfに精通している人のために、またはオレグの可変引数関数アプローチ)のようなもの

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のためにありません第2のアプローチでは、instance PrintfTyp aのように見えてしまいます(タイプが多すぎます)。

もう一度、これは単なる挑戦の問題です:楽しいときにだけそれをしてください。私は間違いなく答えを知りたいと思うだろう。ありがとう!!

答えて

3

書くでしょう。まず第一に、我々はいくつかの拡張が必要になるでしょう:

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

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

import Control.Monad.State 
import Text.Printf 

アイデアは、そのを取り、我々はで与えられた行動にそれを与える、String書式設定を取得するためにprintfに一度の引数の1を供給することです開始。

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 

再帰的ステップは、引数を取り、そして私たちはgに構築しているprintfコールに供給する。

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

コンパイラがこれを推論できるかどうかはわかりません。 (a ->)モナドの別の引数を取るのとは対照的に、文字列がStateTモナドの文脈で印刷されることを期待していることをどのように知っていますか?

おそらく、引数リストが終了したときに型チェッカーを表示する方法を紹介する必要があります。あなたが書いたように、最も簡単な方法は、単に機能でそれをラップすることです:

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

そしてpdebugはモナドで多型である可能性があります。

また、あなたはターミネータを使用してのように、それをスイングすることができるかもしれない:

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

しかし、私はかなりどのように今を見つけ出すことはできません。

+0

うんターミネーターを避けたかったのです。私は、1つの引数だけをサポートすることにもっと興味があるでしょう。すなわち、 'pdebug"という引数がない場合をサポートしていません。しかし、ありがとう。 – gatoatigrado

1

クラスはタイプベースのディスパッチ用です。したがって、put_fooの場合、Text.Printfアーキテクチャは既に満足です(ただし、悲しいことにPrintfTypeをエクスポートしません)。 put_debugput_errについて、あなたはHPrintfTypeと同じ方法でPrintfTypeを一般化することができますが、ハンドルの代わりにString -> IO()機能を取る

{-# 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 

:たとえば、以下はうまく動作するようです。そして、あなたはここでは、既存のText.Printfは、可能なかぎり多くの作業を行うには聞かせしようとする一つの方法だ

pdebug = funPrintf put_debug 
perr = funPrintf put_err 
printf' = funPrintf putStr -- just for fun 
pfoo = printf 
関連する問題