如何避免为执行模式匹配的函数编写样板代码?

时间:2012-04-05 07:37:15

标签: haskell

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])绑定到任意函数是一种非常简单的方法。或者是否有一种完全不同的,更好的方法?

3 个答案:

答案 0 :(得分:12)

Haskell具有多变量函数。想象一下,你有类似

的类型
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"

答案 1 :(得分:2)

您可能希望利用现有库来处理命令行参数。我认为现在的事实标准是cmdargs,但存在其他选项,例如ReadArgsconsole-program

答案 2 :(得分:1)

组合者是你的朋友。试试这个:

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?" ]