2012-04-05 142 views
10

this responseanother question,給出了一個小Haskell代碼草圖,它使用包裝函數來分解一些代碼,用於在命令行參數上進行語法檢查。下面是我正在試圖簡化代碼的一部分:如何避免爲執行模式匹配的函數編寫樣板代碼?

takesSingleArg :: (String -> IO()) -> [String] -> IO() 
takesSingleArg act [arg] = act arg 
takesSingleArg _ _  = showUsageMessage 

takesTwoArgs :: (String -> String -> IO()) -> [String] -> IO() 
takesTwoArgs act [arg1, arg2] = act arg1 arg2 
takesTwoArgs _ _   = showUsageMessage 

有沒有一種方法(可能使用Template Haskell?),以避免編寫額外的函數的參數各是多少?理想情況下,我想能夠寫類似(我在做這個語法上)

generateArgumentWrapper<2, showUsageMessage> 

這擴展到

\fn args -> case args of 
       [a, b] -> fn a b 
       _  -> showUsageMessage 

理想情況下,我甚至可以有不同數量的參數傳遞給generateArgumentWrapper元的功能,這樣我就可以做

generateArgumentWrapper<2, asInt, asFilePath, showUsageMessage> 

這擴展到

\fn args -> case args of 
       [a, b] -> fn (asInt a) (asFilePath b) 
       _  -> showUsageMessage 

有沒有人知道一種方法來實現這一目標?將命令行參數([String])綁定到任意函數將是一種非常簡單的方法。或者可能有一個完全不同的,更好的方法?

回答

12

Haskell有polyvariadic功能。想象一下,你有一個像類型

data Act = Run (String -> Act) | Res (IO()) 

一些功能做你想做

runAct (Run f) x = f x 
runAct (Res _) x = error "wrong function type" 

takeNargs' 0 (Res b) _ = b 
takeNargs' 0 (Run _) _ = error "wrong function type" 
takeNargs' n act (x:xs) = takeNargs' (n-1) (runAct act x) xs 
takeNargs' _ _ [] = error "not long enough list" 

現在是怎樣,你所有你需要的是元帥功能集成到這個Act類型。你需要一些擴展

{-# LANGUAGE FlexibleInstances, FlexibleContexts #-} 

,然後你可以定義

class Actable a where 
    makeAct :: a -> Act 
    numberOfArgs :: a -> Int 

instance Actable (String -> IO()) where 
    makeAct f = Run $ Res . f 
    numberOfArgs _ = 1 

instance Actable (b -> c) => Actable (String -> (b -> c)) where 
    makeAct f = Run $ makeAct . f 
    numberOfArgs f = 1 + numberOfArgs (f "") 

現在你可以定義

takeNArgs n act = takeNargs' n (makeAct act) 

這使得它更容易地定義你的原有功能

takesSingleArg :: (String -> IO()) -> [String] -> IO() 
takesSingleArg = takeNArgs 1 

takesTwoArgs :: (String -> String -> IO()) -> [String] -> IO() 
takesTwoArgs = takeNArgs 2 

但我們甚至可以做到更好

takeTheRightNumArgs f = takeNArgs (numberOfArgs f) f 

令人驚訝的是,這個工程(GHCI)

*Main> takeTheRightNumArgs putStrLn ["hello","world"] 
hello 
*Main> takeTheRightNumArgs (\x y -> putStrLn x >> putStrLn y) ["hello","world"] 
hello 
world 

編輯:上面的代碼要複雜得多它需要。真的,所有你想要的是

class TakeArgs a where 
    takeArgs :: a -> [String] -> IO() 

instance TakeArgs (IO()) where 
    takeArgs a _ = a 

instance TakeArgs a => TakeArgs (String -> a) where 
    takeArgs f (x:xs) = takeArgs (f x) xs 
    takeArgs f [] = error "end of list" 
+0

另請參見標準庫中的Text.Printf,它或多或少地執行相同的操作。請注意,提供錯誤數量的參數是運行時錯誤,而不是類型錯誤。 – 2012-04-12 13:14:51

1

Combinators是你的朋友。試試這個:

take1 :: [String] -> Maybe String 
take1 [x] = Just x 
take1 _ = Nothing 

take2 :: [String] -> Maybe (String,String) 
take2 [x,y] = Just (x,y) 
take2 _ = Nothing 

take3 :: [String] -> Maybe ((String,String),String) 
take3 [x,y,z] = Just ((x,y),z) 
take3 _ = Nothing 

type ErrorMsg = String 

with1 :: (String -> IO()) -> ErrorMsg -> [String] -> IO() 
with1 f msg = maybe (fail msg) f . take1 

with2 :: (String -> String -> IO()) -> ErrorMsg -> [String] -> IO() 
with2 f msg = maybe (fail msg) (uncurry f) . take2 

with3 :: (String -> String -> String -> IO()) -> ErrorMsg -> [String] -> IO() 
with3 f msg = maybe (fail msg) (uncurry . uncurry $ f) . take3 

foo a b c = putStrLn $ a ++ " :: " ++ b ++ " = " ++ c 

bar = with3 foo "You must send foo a name, type, definition" 

main = do 
    bar [ "xs", "[Int]", "[1..3]" ] 
    bar [ "xs", "[Int]", "[1..3]", "What am I doing here?" ] 

,如果你喜歡制服語言擴展:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, UndecidableInstances #-} 

foo a b c = putStrLn $ a ++ " :: " ++ b ++ " = " ++ c 
foo_msg = "You must send foo a name, type, definition" 

class ApplyArg a b | a -> b where 
    appArg :: ErrorMsg -> a -> [String] -> IO b 

instance ApplyArg (IO b) b where 
    appArg _msg todo [] = todo 
    appArg msg _todo _ = fail msg 

instance ApplyArg v q => ApplyArg (String -> v) q where 
    appArg msg todo (x:xs) = appArg msg (todo x) xs 
    appArg msg _todo _ = fail msg 

quux :: [String] -> IO() 
quux xs = appArg foo_msg foo xs 

main = do 
    quux [ "xs", "[int]", "[1..3]" ] 
    quux [ "xs", "[int]", "[1..3]", "what am i doing here?" ] 
2

你可能想要利用現有的庫來處理命令行參數。我相信現在的實際標準是cmdargs,但也有其他選擇,例如ReadArgsconsole-program

相關問題