2013-08-14 41 views
6

Tôi muốn gọi hàm printf của Text.Printf bằng mảng nhưng tôi không thể tìm được cách. Dưới đây là hai phiên bản không hoạt động (thực sự giống như ý tưởng).Đối số printf Haskell dưới dạng mảng

lỗi
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 là:

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 

(Tại sao:. Tôi đang viết DSL và muốn cung cấp chức năng printf)

+2

Chỉ cần cung cấp thông tin, có một vực thẳm của sự khác biệt giữa một danh sách liên kết (mà '[a]' là) và mảng. –

+3

Bạn có thực sự muốn ép buộc tất cả các đối số cho printf có cùng loại không? – augustss

+0

Không, tất cả đối số là trường hợp của PrintfArg và không có cùng loại. –

Trả lời

14

Đầu tiên, nhận ra rằng PrintfArg a => [a] không phải là danh sách không đồng nhất. Tức là, mặc dù IntString là cả hai trường hợp PrintfArg, [ 1 :: Int, "foo" ] không phải là cấu trúc hợp lệ.

Vì vậy, nếu bạn đã xác định hàm :: PrintfArg a => String -> [a] -> String, thì tất cả các arg sẽ bị ràng buộc là cùng loại.

Để giải quyết vấn đề này, bạn có thể sử dụng định lượng hiện tại.

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

Lý do giải pháp đầu tiên của bạn không hiệu quả là vì bạn đã vượt qua res để tham gia.

Khi bạn có foo :: Constraint a => a -> t bạn đảm bảo rằng foo sẽ hoạt động trên tất cả trường hợp Constraint. Và mặc dù tồn tại một thể hiện của PrintfType mà có thể lấy một đối số, không phải tất cả các trường hợp đều có thể. Vì vậy, lỗi trình biên dịch của bạn.

Ngược lại, khi bạn có foo :: Constraint a => t -> a, bạn đảm bảo rằng foo sẽ trả lại bất kỳ trường hợp mong muốn nào là Constraint. Một lần nữa, người gọi sẽ chọn trường hợp nào. Đây là lý do mã của tôi hoạt động - khi printfa' đệ quy, nó yêu cầu cuộc gọi đệ quy để trả về một giá trị từ cá thể (PrintfArg a, PrintfType t) => a -> t.

Để thử lần thứ hai, trình biên dịch than phiền vì foldr yêu cầu giá trị tích lũy phải cùng loại giữa các lần lặp. GHC nhận thấy rằng giá trị tích lũy phải là một loại hàm (PrintfArg a, PrintfType t) => a -> t, bởi vì bạn áp dụng nó trong hàm lặp lại. Nhưng bạn trả lại giá trị được áp dụng, mà nó có thể tìm ra là loại t. Điều này có nghĩa là t bằng a -> t, mà GHC không thích, bởi vì nó không cho phép các loại vô hạn. Vì vậy, nó phàn nàn.

Nếu bạn muốn sử dụng một nếp gấp, bạn có thể, bạn chỉ cần che chắn loại bộ tích lũy bằng cách sử dụng Rank2Types hoặc RankNTypes để giữ cho loại liên tục giữa các lần lặp.

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

Tôi không chắc chắn này là một giải pháp tối thiểu, nhưng nếu bạn biết độ dài của các vectơ tĩnh, bạn có thể sử dụng các loại được đánh chỉ mục loại Vec và loại được lập chỉ mục 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) 

thì bạn có thể làm run (Fun $ printf "%s %s") z.

+0

Bạn đang cố gắng đạt được điều gì ở đây? – augustss

+0

Ồ. Tôi đã cố gắng để làm với hung hăng đã kết thúc làm, nhưng không nghĩ đến việc sử dụng các loại tồn tại. Chỉ mục là đủ thông tin để biết rằng không có loại vô hạn nào đang được xây dựng. –

0

Đây là của tôi.

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 

Ví dụ:

> printfList "%s%s%s" ["a","b","c"] 
"abc" 
Các vấn đề liên quan