2012-03-05 13 views
5

Questo è un problema di sfida più che un problema utile (ho trascorso alcune ore su di esso). Dato alcune funzioni,Come scrivere una famiglia di funzioni printf (debug print, ecc.) In Haskell

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

Voglio scrivere una funzione printf generalizzata, lo chiamano gprint, in modo tale che posso scrivere

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

e quindi utilizzare pdebug, perr, e pfoo come printf, per esempio ,

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

Non riesco a trovare una classe sufficientemente generale. I miei tentativi sono stati le cose come (per chi ha familiarità con Printf, o un approccio Funzione Variadica di Oleg)

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

o

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

Entrambi sono troppo difficile da scrivere le istanze di base per: non c'è buona scelta per r per il primo approccio (e, il suo tipo non si riflette nella famiglia di tipi indicizzati non -injective AppendArg), e nel secondo approccio, uno finisce per scrivere instance PrintfTyp a che sembra sbagliato (corrisponde a troppi tipi).

Anche in questo caso, è solo un problema di sfida: farlo solo se è divertente. Sarei sicuramente curioso di sapere la risposta però. Grazie!!

risposta

3

Ecco un approccio che cerca di lasciare che l'attuale Text.Printf di fare come gran parte del lavoro possibile.Prima di tutto, abbiamo bisogno di alcune estensioni:

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

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

import Control.Monad.State 
import Text.Printf 

L'idea è quella di alimentare l'argomenti uno alla volta in printf per ottenere il formato String, poi prendere quella e dare all'azione ci hanno dato al inizio.

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 

Il passo ricorsivo prende un argomento, e la immette alla printf chiamata che stiamo costruendo in 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) 

I casi di base solo alimentano la stringa risultante in 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 

Ecco il programma di test che ho usato:

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 

E l'output:

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

Non sono sicuro che il compilatore sarà in grado di dedurre questo. Come fa a sapere che stai aspettando che la stringa venga stampata nel contesto di una monade StateT, invece di prendere un altro argomento nella monade (a ->).

Probabilmente sarà necessario introdurre un modo per mostrare il controllo di tipo al termine dell'elenco degli argomenti. Il modo più semplice è quello di avvolgere solo in una funzione, in modo da scrivere:

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

E poi pdebug potrebbe essere polimorfico nella monade.

Si potrebbe anche essere in grado di oscillare in modo da utilizzare un terminatore, come:

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

Ma io non riesco a capire come in questo momento.

+0

Sì , Volevo evitare i terminatori. Sarei più interessato a supportare un solo argomento, cioè non supportare il caso "pdebug" senza argomenti "". Grazie comunque. – gatoatigrado

1

Le classi sono per la spedizione basata su tipi. Quindi, per put_foo, l'architettura Text.Printf è già soddisfacente (anche se non viene esportata PrintfType, purtroppo). Ad esempio, il seguente sembra funzionare bene:

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

Per put_debug e put_err, è possibile generalizzare la PrintfType nello stesso modo HPrintfType fa, ma prendendo una funzione String -> IO() invece di una maniglia. Poi si può scrivere

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