2012-03-05 22 views
5

Đây là một vấn đề thách thức hơn là một vấn đề hữu ích (tôi đã dành một vài giờ trên đó). Đưa ra một số chức năng,Cách viết một họ các chức năng printf (gỡ lỗi in, vv) trong Haskell

put_debug, put_err :: String -> IO() 
put_foo :: String -> StateT [String] m() 

Tôi muốn viết một hàm printf tổng quát, gọi nó là gprint, như vậy tôi có thể viết

pdebug = gprint put_debug 
perr = gprint put_err 
pfoo = gprint put_foo 

và sau đó sử dụng pdebug, perr, và pfoo như printf, ví dụ ,

pdebug "Hi" 
pdebug "my value: %d" 1 
pdebug "two values: %d, %d" 1 2 

Tôi không thể xoay xở để có được một lớp học tổng quát đầy đủ. nỗ lực của tôi đã được điều tương tự (đối với những người quen thuộc với Printf, hoặc cách tiếp cận chức năng variadic Oleg của)

class PrintfTyp r where 
    type AppendArg r a :: * 
    spr :: (String -> a) -> String -> [UPrintf] -> AppendArg r a 

hoặc

class PrintfTyp r where 
    type KRetTyp r :: * 
    spr :: (String -> KRetTyp r) -> String -> [UPrintf] -> r 

Cả hai đều quá khó khăn để viết các trường hợp cơ sở cho: không có lựa chọn tốt cho r cho cách tiếp cận đầu tiên (và, kiểu của nó không được phản ánh trong họ loại được đánh chỉ mục không tiêm), và trong phương pháp thứ hai, một kết thúc bằng văn bản instance PrintfTyp a trông sai (khớp với quá nhiều loại).

Một lần nữa, nó chỉ là một vấn đề thách thức: làm điều đó chỉ khi đó là niềm vui. Tôi chắc chắn sẽ tò mò muốn biết câu trả lời. Cảm ơn!!

Trả lời

3

Đây là một cách tiếp cận cố gắng để làm cho càng nhiều công việc càng tốt.Trước hết, chúng ta sẽ cần một số phần mở rộng:

{-# LANGUAGE TypeFamilies #-} 
{-# LANGUAGE FlexibleContexts #-} 

-- To avoid having to write some type signatures. 
{-# LANGUAGE NoMonomorphismRestriction #-} 
{-# LANGUAGE ExtendedDefaultRules #-} 

import Control.Monad.State 
import Text.Printf 

Ý tưởng là để nuôi các đối số cùng một lúc vào printf để có được những định dạng String, sau đó đi đó và đưa nó cho hành động chúng tôi đã đưa ra ở khởi đầu.

gprint :: GPrintType a => (String -> EndResult a) -> String -> a 
gprint f s = gprint' f (printf s) 

class PrintfType (Printf a) => GPrintType a where 
    type Printf a :: * 
    type EndResult a :: * 
    gprint' :: (String -> EndResult a) -> Printf a -> a 

Bước đệ quy có một đối số, và thức ăn nó vào printf gọi chúng tôi đang xây dựng lên trong g.

instance (PrintfArg a, GPrintType b) => GPrintType (a -> b) where 
    type Printf (a -> b) = a -> Printf b 
    type EndResult (a -> b) = EndResult b 
    gprint' f g x = gprint' f (g x) 

Các trường hợp cơ sở chỉ ăn chuỗi kết quả vào f:

instance GPrintType (IO a) where 
    type Printf (IO a) = String 
    type EndResult (IO a) = IO a 
    gprint' f x = f x 

instance GPrintType (StateT s m a) where 
    type Printf (StateT s m a) = String 
    type EndResult (StateT s m a) = StateT s m a 
    gprint' f x = f x 

Đây là chương trình thử nghiệm tôi đã sử dụng:

put_debug, put_err :: String -> IO() 
put_foo :: Monad m => String -> StateT [String] m() 

put_debug = putStrLn . ("DEBUG: " ++) 
put_err = putStrLn . ("ERR: " ++) 
put_foo x = modify (++ [x]) 

pdebug = gprint put_debug 
perr = gprint put_err 
pfoo = gprint put_foo 

main = do 
    pdebug "Hi" 
    pdebug "my value: %d" 1 
    pdebug "two values: %d, %d" 1 2 
    perr "ouch" 
    execStateT (pfoo "one value: %d" 42) [] >>= print 

Và kết quả:

DEBUG: Hi 
DEBUG: my value: 1 
DEBUG: two values: 1, 2 
ERR: ouch 
["one value: 42"] 
0

Tôi không chắc trình biên dịch có thể suy ra điều này hay không. Làm thế nào để biết rằng bạn đang mong đợi chuỗi được in trong ngữ cảnh của một đơn vị StateT, trái với việc tham gia một đối số khác trong đơn vị (a ->).

Có thể bạn sẽ cần giới thiệu cách hiển thị trình kiểm tra loại khi danh sách đối số đã kết thúc. Cách đơn giản nhất là chỉ quấn nó vào một hàm, vì vậy bạn viết:

pdebug $ printf "%d %d %d" 1 2 3 

Và sau đó pdebug có thể đa hình trong đơn nguyên.

Bạn cũng có thể có thể xoay nó để bạn sử dụng một terminator, như:

data Kthx = Kthx 
printf "%d %d %d" 1 2 3 Kthx 

Nhưng tôi không thể khá tìm ra cách ngay bây giờ.

+0

Ừ , Tôi muốn tránh các terminators. Tôi muốn được quan tâm nhiều hơn chỉ hỗ trợ một đối số, tức là không hỗ trợ trường hợp 'pdebug 'không có đối số" '. Cảm ơn mặc dù. – gatoatigrado

1

Lớp học dành cho công văn dựa trên loại. Vì vậy, đối với put_foo, kiến ​​trúc Text.Printf đã thỏa mãn (mặc dù nó không xuất PrintfType, thật đáng buồn). Ví dụ, sau đây dường như làm việc tốt:

{-# LANGUAGE TypeFamilies #-} -- for ~ syntax 
import Control.Monad.State 
import Data.Default 

-- copy and paste source of Text.Printf here 

put_foo :: String -> StateT [String] m() 
put_foo = undefined 

instance (Default a, Monad m, s ~ [String]) => PrintfType (StateT s m a) where 
    spr s us = put_foo (spr s us) >> return def 

cho put_debugput_err, bạn có thể khái quát các PrintfType trong cùng một cách HPrintfType có, nhưng tham gia một chức năng String -> IO() thay vì một tay cầm. Sau đó, bạn sẽ viết

pdebug = funPrintf put_debug 
perr = funPrintf put_err 
printf' = funPrintf putStr -- just for fun 
pfoo = printf 
Các vấn đề liên quan