我想用数组调用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"]
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
(为什么:我正在编写DSL并希望提供printf功能.)
ram*_*ion 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" ]
Run Code Online (Sandbox Code Playgroud)
你的第一个解决方案不起作用的原因是因为你res作为一个参数传递给step.
如果你有foo :: Constraint a => a -> t保证foo将适用于所有的实例Constraint.虽然存在一个PrintfType可以进行参数的实例,但并非所有实例都可以.因此你的编译错误.
相反,当你有foo :: Constraint a => t -> a,你保证foo将返回任何所需的实例Constraint.同样,调用者可以选择哪个实例.这就是我的代码工作的原因 - 当printfa'recurses时,它需要递归调用从(PrintfArg a, PrintfType t) => a -> t实例返回一个值.
对于您的第二次尝试,编译器会抱怨因为foldr要求迭代之间的累积值具有相同的类型.GHC注意到累积值必须是函数类型(PrintfArg a, PrintfType t) => a -> t,因为您在迭代函数中应用它.但是你返回应用的值,它可以计算出类型t.这意味着GHC不喜欢的t等于a -> t,因为它不允许无限类型.所以它抱怨.
如果你想使用折叠,你可以,你只需要使用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)
Run Code Online (Sandbox Code Playgroud)