2012-03-05 2 views
5

Ceci est un problème de challenge plus qu'un problème utile (j'ai passé quelques heures dessus). Compte tenu de certaines fonctions,Comment écrire une famille de fonctions printf (impression de débogage, etc.) dans Haskell

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

Je veux écrire une fonction printf généralisée, appelez-GPRINT, de sorte que je peux écrire

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

puis utilisez pdebug, perr et pfoo comme printf, par exemple ,

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

Je n'arrive pas à trouver une classe suffisamment générale. Mes tentatives ont été des choses comme (pour ceux qui connaissent Printf, ou approche de la fonction variadique Oleg)

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

ou

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

Les deux sont trop difficiles à écrire des instances de base pour: il n'y a pas un bon choix pour r pour la première approche (et, son type n'est pas reflété dans la famille de types indexés non-injectifs AppendArg), et dans la deuxième approche, on finit par écrire instance PrintfTyp a qui semble faux (correspond à trop de types).

Encore une fois, c'est juste un problème de défi: faites-le seulement si c'est amusant. Je serais certainement curieux de connaître la réponse si. Merci!!

Répondre

3

Voici une approche qui tente de laisser le Text.Printf existant pour faire autant de travail que possible.Tout d'abord, nous aurons besoin de quelques extensions:

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

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

import Control.Monad.State 
import Text.Printf 

L'idée est de nourrir les arguments un à la fois dans printf pour obtenir le format String, puis prendre cela et donner à l'action nous a donné à la début.

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 

L'étape récursive prend un argument, et il se nourrit à l'appel printf nous construisons en 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) 

Les cas de base alimentent simplement la chaîne résultante en 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 

Voici le programme de test je:

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 

Et la sortie:

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

Je ne suis pas sûr que le compilateur sera en mesure de déduire cela. Comment sait-on que vous attendez que la chaîne soit imprimée dans le contexte d'une monade StateT, au lieu de prendre un autre argument dans la monade (a ->)?

Vous devrez probablement introduire un moyen d'afficher le vérificateur de type lorsque la liste d'arguments est terminée. La façon la plus simple est d'envelopper juste dans une fonction, vous écrivez donc:

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

Et pdebug pourrait être polymorphes dans la monade.

Vous pourriez également être en mesure de se balancer si vous utilisez un terminateur, comme:

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

Mais je ne peux pas tout à fait comprendre comment en ce moment.

+0

Oui , Je voulais éviter les terminateurs. Je serais plus intéressé à ne soutenir qu'un seul argument, c'est-à-dire ne pas soutenir le cas 'pdebug 'pas d'arguments" '. Merci quand même. – gatoatigrado

1

Les classes sont pour l'envoi basé sur le type. Ainsi, pour put_foo, l'architecture Text.Printf est déjà satisfaisante (bien qu'elle n'exporte pas PrintfType, malheureusement). Par exemple, ce qui suit semble bien fonctionner:

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

Pour put_debug et put_err, vous pouvez généraliser l'PrintfType de la même manière HPrintfType fait, mais en prenant une fonction String -> IO() au lieu d'une poignée. Ensuite, vous écririez

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