2012-03-05 18 views
5

Jest to trudniejszy problem niż pożyteczny problem (spędziłem na nim kilka godzin). Biorąc pod uwagę niektóre funkcje,Jak napisać rodzinę funkcji printf (wydruk debugowania itp.) W Haskell

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

Chcę napisać uogólnionej funkcji printf, nazywają to gprint tak, że mogę napisać

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

a następnie użyć pdebug, perr i pfoo jak printf, na przykład ,

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

Nie uda mi się wymyślić wystarczająco ogólnej klasy. Moje próby były takie rzeczy jak (dla tych, którzy znają Printf lub zmiennej liczbie argumentów podejścia funkcyjnego Olega)

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

lub

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

Oba są zbyt trudne do napisania wystąpień bazowe dla: nie ma dobry wybór dla r w przypadku pierwszego podejścia (i jego typ nie znajduje odzwierciedlenia w rodzinie z indeksem typu iniekcyjnego AppendArg), aw drugim podejściu kończy się pisanie instance PrintfTyp a, które wygląda źle (dopasowuje zbyt wiele typów).

Ponownie, to tylko problem z wyzwaniem: rób to tylko wtedy, gdy jest fajnie. Zdecydowanie chciałbym poznać odpowiedź. Dzięki!!

Odpowiedz

3

Oto jedno podejście, które próbuje niech istniejąca Text.Printf zrobić jak dużo pracy, jak to możliwe.Po pierwsze, musimy pewne rozszerzenia:

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

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

import Control.Monad.State 
import Text.Printf 

Chodzi o to, aby nakarmić jedną argumenty naraz do printf uzyskać sformatowanego String, a następnie podjąć to i dać go do działania dostaliśmy u początek.

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 

Rekurencyjne krok przyjmuje argument, i podaje go do printf rozmowy Budujemy w 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) 

Przypadki bazowe tylko karmić wynikowy ciąg znaków w 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 

Oto program testów użyłem:

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 

a wyjście:

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

Nie jestem pewien, czy kompilator będzie w stanie to wydedukować. Skąd wiadomo, że oczekujesz, że napis zostanie wydrukowany w kontekście monady StateT, w przeciwieństwie do podjęcia kolejnego argumentu w monadzie (a ->).

Najprawdopodobniej konieczne będzie wprowadzenie sposobu pokazania kontrolera, gdy lista argumentów się zakończy. Najprostszym sposobem jest po prostu zawinąć go w funkcji, więc napisać:

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

A potem pdebug może być polimorficzny w monady.

może także być w stanie odchylić go więc użyć terminatora, jak:

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

Ale nie mogę dość dowiedzieć się, jak teraz.

+0

Tak , Chciałem uniknąć terminatorów. Byłbym bardziej zainteresowany poparciem tylko jednego argumentu, tj. Nie popierania sprawy "pdebug" bez argumentów "'. W każdym razie dzięki. – gatoatigrado

1

Klasy są dla wysyłek opartych na typie. Tak więc, dla architektury put_foo, architektura Text.Printf jest już zadowalająca (chociaż nie eksportuje ona PrintfType, niestety). Na przykład, następujące wydaje się działać dobrze:

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

dla put_debug i put_err można uogólnić PrintfType w taki sam sposób HPrintfType robi, ale biorąc String -> IO() funkcję zamiast uchwytu. Potem piszesz

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