7

我想用数组调用 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 功能。)

4

3 回答 3

15

首先,意识到这PrintfArg a => [a]不是一个异类列表。也就是说,即使IntString都是 的实例PrintfArg[ 1 :: Int, "foo" ]也不是有效的构造。

因此,如果您确实定义了一个 function :: 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将 step 作为参数传递。

当你有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,因为你在迭代函数中应用了它。但是您返回应用的值,它可以确定是 type t。这意味着GHC 不喜欢的tequals a -> t,因为它不允许无限类型。所以它抱怨。

如果你想使用折叠,你可以,你只需要使用Rank2TypesRankNTypes在迭代之间保持类型不变来屏蔽累加器类型。

{-# 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)
于 2013-08-15T01:54:37.180 回答
2

我不确定这是一个最小的解决方案,但是如果您静态知道向量的长度,则可以使用类型索引的Vectors 和类型索引的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

于 2013-08-15T01:11:05.927 回答
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"
于 2016-08-18T18:49:09.980 回答