2013-08-14 18 views
6

Ich möchte Text.Printf Funktion printf mit Array aufrufen, aber ich kann keinen Weg finden. Hier sind zwei nicht funktionierende Versionen (eigentlich gleiche Idee).Haskell printf Argumente als 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 Fehler sind:

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 

(Warum. Ich DSL Schreiben und printf-Funktion zur Verfügung stellen möchten)

+2

Gerade für Informationen gibt es einen Abgrund der Differenz zwischen einer verknüpften Liste (die '[a]' ist) und Array. –

+3

Möchten Sie wirklich alle Argumente auf printf zwingen, um den gleichen Typ zu haben? – augustss

+0

Nein, alle Argumente sind Instanzen von PrintfArg und haben nicht denselben Typ. –

Antwort

14

Zuerst erkennen, dass PrintfArg a => [a] keine heterogene Liste ist. Das heißt, obwohl Int und String beide Instanzen von PrintfArg sind, ist [ 1 :: Int, "foo" ] kein gültiges Konstrukt.

Wenn Sie also eine Funktion :: PrintfArg a => String -> [a] -> String definiert haben, würden alle Argumente vom selben Typ sein.

Um dies zu umgehen, können Sie existenzielle Quantifizierung verwenden.

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

Der Grund Ihre erste Lösung nicht ist nicht funktioniert, weil Sie res bestanden als Argument zu treten.

Wenn Sie foo :: Constraint a => a -> t haben Sie garantieren, dass foo auf alle Instanzen Constraint arbeiten. Und obwohl es eine Instanz von PrintfType gibt, die ein Argument nehmen kann, können nicht alle Instanzen. Also dein Compilerfehler. Wenn Sie also foo :: Constraint a => t -> a haben, garantieren Sie, dass foo jede gewünschte Instanz von Constraint zurückgibt. Auch hier kann der Anrufer wählen, welche Instanz er wählt. Das ist der Grund, warum mein Code funktioniert - wenn printfa' rekursiv ist, muss der rekursive Aufruf einen Wert von der (PrintfArg a, PrintfType t) => a -> t Instanz zurückgeben.

Für Ihren zweiten Versuch beschwert sich der Compiler, weil foldr erfordert, dass der akkumulierte Wert zwischen Iterationen des gleichen Typs ist. GHC stellt fest, dass der akkumulierte Wert ein Funktionstyp (PrintfArg a, PrintfType t) => a -> t sein muss, da Sie es in der wiederholten Funktion anwenden. Aber Sie geben den angewandten Wert zurück, den Sie herausfinden können, ist vom Typ t. Dies bedeutet, dass t entspricht, was GHC nicht gefällt, da es keine unendlichen Typen zulässt. Also beschwert es sich.

Wenn Sie eine Faltung verwenden möchten, können Sie den Akkumulatortyp nur mit Rank2Types oder RankNTypes maskieren, um den Typ zwischen den Iterationen konstant zu halten.

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

Ich bin nicht sicher, dass eine Minimallösung, aber Wenn Sie die Länge Ihrer Vektoren statisch kennen, können Sie typenindizierte Vec Toren verwenden und indexierte Fun Typen eingeben.

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

dann können Sie run (Fun $ printf "%s %s") z tun.

+0

Was versuchen Sie hier zu erreichen? – augustss

+0

Oh. Ich habe versucht mit rampion zu tun, aber ich dachte nicht, existentielle Typen zu verwenden. Der Index ist genug Information, um zu wissen, dass kein unendlicher Typ konstruiert wird. –

0

Hier ist meiner.

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 

Beispiel:

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