我想用数组调用Text.Printf函数printf但我找不到方法。 这是两个不起作用的版本(实际上是相同的想法)。
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错误是:
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
(为什么:我正在编写DSL并希望提供printf功能。)
答案 0 :(得分:14)
首先,要意识到PrintfArg a => [a]
不是异类列表。也就是说,即使Int
和String
都是PrintfArg
的实例,[ 1 :: Int, "foo" ]
也不是有效的构造。
因此,如果你确定了一个函数:: PrintfArg a => String -> [a] -> String
,那么所有的args都会被约束为相同的类型。
为了解决这个问题,你可以使用存在量化。
{-# 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" ]
您的第一个解决方案无效的原因是您将res
作为参数传递给了步骤。
如果您有foo :: Constraint a => a -> t
,则可以保证foo可以在 Constraint
的所有实例上运行。虽然存在PrintfType
可以接受参数的实例,但并非所有实例都可以。因此你的编译错误。
相反,当你有foo :: Constraint a => t -> a
时,你保证foo将返回任何所需的Constraint
实例。同样,调用者可以选择哪个实例。这就是我的代码有效的原因 - 当printfa'
递归时,它需要递归调用才能从(PrintfArg a, PrintfType t) => a -> t
实例返回一个值。
对于第二次尝试,编译器会抱怨,因为foldr
要求迭代之间的累计值具有相同的类型。 GHC注意到累积值必须是函数类型(PrintfArg a, PrintfType t) => a -> t
,因为您在迭代函数中应用它。但是,您返回应用的值,它可以确定类型为t
。这意味着t
等于a -> t
,这是GHC不喜欢的,因为它不允许无限类型。所以它抱怨。
如果你想使用折叠,你可以使用Rank2Types
或RankNTypes
来掩盖累加器类型,以便在迭代之间保持类型不变。
{-# 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)
答案 1 :(得分:2)
我不确定这是一个最小的解决方案,但如果您静态知道矢量的长度,则可以使用类型索引Vec
转子并键入索引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)
然后你可以run (Fun $ printf "%s %s") z
。
答案 2 :(得分:0)
这是我的。
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
示例:
> printfList "%s%s%s" ["a","b","c"]
"abc"