2013-08-14 14 views
6

Voglio chiamare la funzione Text.Printf printf con array ma non riesco a trovare un modo. Qui ci sono due versioni non funzionanti (in realtà la stessa idea).Argomenti haskell printf come array

import Text.Printf 

printfa :: (PrintfArg a) => String -> [a] -> String 
printfa format args = step (printf format) args 
    where 
    step :: (PrintfType r, PrintfArg a) => r -> [a] -> r 
    step res (x:[]) = res x 
    step res (x:xs) = step (res x) xs 

printfa' :: (PrintfArg a) => String -> [a] -> String 
printfa' format args = foldr (\arg p -> p arg) (printf format) args 

main = putStrLn $ printfa "%s %s" ["Hello", "World"] 

GHC errori sono:

printfa.hs:8:23: 
    Couldn't match type `r' with `a1 -> r' 
     `r' is a rigid type variable bound by 
      the type signature for 
      step :: (PrintfType r, PrintfArg a1) => r -> [a1] -> r 
      at printfa.hs:8:5 
    The function `res' is applied to one argument, 
    but its type `r' has none 
    In the expression: res x 
    In an equation for `step': step res (x : []) = res x 

printfa.hs:12:41: 
    The function `p' is applied to one argument, 
    but its type `String' has none 
    In the expression: p arg 
    In the first argument of `foldr', namely `(\ arg p -> p arg)' 
    In the expression: foldr (\ arg p -> p arg) (printf format) args 

(Perché:. Sto scrivendo DSL e desidera fornire funzione printf)

+2

Solo per informazioni, c'è un abisso di differenza tra una lista collegata (che '[a]' è) e la matrice. –

+3

Vuoi veramente forzare tutti gli argomenti su printf ad avere lo stesso tipo? – augustss

+0

No, tutti gli argomenti sono istanze di PrintfArg e non hanno lo stesso tipo. –

risposta

14

Innanzitutto, rendersi conto che PrintfArg a => [a] non è un elenco eterogeneo. Cioè, anche se Int e String sono entrambe le istanze di PrintfArg, [ 1 :: Int, "foo" ] non è un costrutto valido.

Quindi, se si definisse una funzione :: PrintfArg a => String -> [a] -> String, tutti gli argomenti sarebbero vincolati allo stesso tipo.

Per aggirare questo problema, è possibile utilizzare la quantificazione esistenziale.

{-# LANGUAGE ExistentialQuantification #-} 
import Text.Printf 

data PrintfArgT = forall a. PrintfArg a => P a 

printfa :: PrintfType t => String -> [ PrintfArgT ] -> t 
printfa format = printfa' format . reverse 
    where printfa' :: PrintfType t => String -> [ PrintfArgT ] -> t 
     printfa' format [] = printf format 
     printfa' format (P a:as) = printfa' format as a 

main = do 
    printfa "hello world\n" [] 
    printfa "%s %s\n" [ P "two", P "strings"] 
    printfa "%d %d %d\n" (map P $ [1 :: Int, 2, 3]) 
    printfa "%d %s\n" [ P (1 :: Int), P "is the loneliest number" ] 

La ragione per la vostra prima soluzione non ha funzionato è perché avete passato res al passaggio come argomento.

Quando hai foo :: Constraint a => a -> t si garantire che foo funzionerà su tutti i istanze di Constraint. E anche se esiste un'istanza di PrintfType che può accettare un argomento, non tutte le istanze possono. Quindi il tuo errore del compilatore.

Al contrario, se si dispone di foo :: Constraint a => t -> a, si garantisce che foo restituirà qualsiasi istanza desiderata di Constraint. Di nuovo, il chiamante deve scegliere quale istanza. Questo è il motivo per cui il mio codice funziona - quando ricorre il printfa', richiede la chiamata ricorsiva per restituire un valore dall'istanza (PrintfArg a, PrintfType t) => a -> t.

Per il secondo tentativo, il compilatore si lamenta perché foldr richiede che il valore accumulato sia dello stesso tipo tra le iterazioni. GHC rileva che il valore accumulato deve essere un tipo di funzione (PrintfArg a, PrintfType t) => a -> t, poiché viene applicato nella funzione iterata. Ma si restituisce il valore applicato, che è in grado di capire è di tipo t. Ciò significa che t equivale a a -> t, a cui GHC non piace, perché non consente tipi infiniti. Quindi si lamenta.

Se si desidera utilizzare una piega, è possibile, è sufficiente mascherare il tipo di accumulatore utilizzando Rank2Types o RankNTypes per mantenere il tipo costante tra le iterazioni.

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE RankNTypes #-} 
import Text.Printf 

data PrintfArgT = forall a. PrintfArg a => P a 
data PrintfTypeT = T { unT :: forall r. PrintfType r => r } 

printfa :: PrintfType t => String -> [ PrintfArgT ] -> t 
printfa format = unT . foldl (\(T r) (P a) -> T $ r a) (T $ printf format) 
2

Non sono sicuro che questa è una soluzione minimale, ma se si conosce la lunghezza dei vettori in modo statico, è possibile utilizzare i numeri indicizzati al tipo Vec e digitare i tipi indicizzati Fun.

{-# LANGUAGE GADTs, TypeFamilies #-} 

import Text.Printf 

data Z 
data S n 

data Vec n a where 
    Nil :: Vec Z a 
    Cons :: a -> Vec n a -> Vec (S n) a 

type family Fn n b a 
type instance Fn Z b a = a 
type instance Fn (S n) b a = b -> Fn n b a 

-- in order to tell the compiler that we want to consider a function as a `Fn` 
newtype Fun n b a = Fun (Fn n b a) 

run :: Fun n b a -> Vec n b -> a 
run (Fun f) v = case v of 
    Nil   -> f 
    Cons b more -> run (Fun $ f b) more 

z :: Vec (S (S Z)) String 
z = Cons "foo" (Cons "bar" Nil) 

quindi è possibile fare run (Fun $ printf "%s %s") z.

+0

Cosa stai cercando di ottenere qui? – augustss

+0

Oh. Stavo cercando di fare con rampion finito, ma non pensavo di usare tipi esistenziali. L'indice è abbastanza informazioni per sapere che nessun tipo infinito viene costruito. –

0

Ecco il mio.

import Text.Printf (printf, PrintfType) 

printfList_ :: PrintfType t => String -> [String] -> Int -> t 
printfList_ string list n | n == 0 = printf string (list !! 0) 
          | otherwise = (printfList_ string list (n - 1)) (list !! n) 

printfList :: String -> [String] -> String 
printfList string list = (printfList_ string list (length list - 1)) :: String 

Esempio:

> printfList "%s%s%s" ["a","b","c"] 
"abc" 
Problemi correlati