在this response到another 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]
)绑定到任意函数是一种非常简单的方法。或者是否有一种完全不同的,更好的方法?
答案 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,但存在其他选项,例如ReadArgs和console-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?" ]