如何避免为执行模式匹配的函数编写样板代码? [英] How can I avoid writing boilerplate code for functions performing pattern matching?
问题描述
在此回复至另一个问题,给出了一些Haskell代码草图,它使用包装函数将一些代码用于对命令行参数进行语法检查。以下是我试图简化的部分代码:
takesSingleArg ::(String - > IO()) - > [字符串] - > IO()
takesSingleArg act [arg] = act arg
takesSingleArg _ _ = showUsageMessage
takesTwoArgs ::(String - > String - > IO()) - > ; [字符串] - > IO()
takesTwoArgs act [arg1,arg2] = act arg1 arg2
takesTwoArgs _ _ = showUsageMessage
有没有办法(可能使用模板Haskell ?)来避免为每个参数编写额外的函数?理想情况下,我希望能够写出类似于(我正在创建此语法)
generateArgumentWrapper <2, showUsageMessage>
然后扩展为
\fn args - >案例参数
[a,b] - > fn a b
_ - > showUsageMessage
理想情况下,我甚至可以为 generateArgumentWrapper设置可变数量的参数
元函数,以便我可以做
pre $ generateArgumentWrapper< 2,asInt,asFilePath,showUsageMessage>
然后扩展为
\fn args - >案例参数
[a,b] - > fn(asInt a)(asFilePath b)
_ - > showUsageMessage
是否有人知道实现此目的的方法?将命令行参数( [String]
)绑定到任意函数将是一种非常简单的方法。或者可能有一个完全不同的,更好的方法?
Haskell具有多变量函数。想象一下,你有一个类似于
data Act = Run(String - > Act)| Res(IO())
有一些功能可以做你想做的事
runAct(Run f)x = fx
runAct(Res _)x =错误错误的函数类型
takeNargs'0(Res b)_ = b
takeNargs'0(Run _)_ = error错误的函数类型
takeNargs'n act(x:xs)= takeNargs'(n-1) (runAct act x)xs
takeNargs'_ _ [] = error列表不够长
现在,你所需要的只是把函数编入这个 Act
类型。您需要一些扩展名
{ - #LANGUAGE FlexibleInstances,FlexibleContexts# - }
然后你可以定义
class Actable a where
makeAct :: a - > Act
numberOfArgs :: a - > Int
实例Actable(String - > IO())其中
makeAct f =运行$ Res。 f
numberOfArgs _ = 1
实例可实行(b - > c)=> Actable(String - >(b - > c))其中
makeAct f =运行$ makeAct。 f
numberOfArgs f = 1 + numberOfArgs(f)
现在您可以定义
takeNArgs n act = takeNargs'n(makeAct act)
这样可以更容易地定义您的原始函数
takesSingleArg ::字符串 - > IO()) - > [字符串] - > IO()
takesSingleArg = takeNArgs 1
takesTwoArgs ::(String - > String - > IO()) - > [字符串] - > IO()
takesTwoArgs = takeNArgs 2
但我们可以做得更好
takeTheRightNumArgs f = takeNArgs(numberOfArgs f)f
令人惊讶的是,这项工作(GHCI)
* Main> takeTheRightNumArgs putStrLn [hello,world]
hello
* Main> takeTheRightNumArgs(\xy - > putStrLn x>> putStrLn y)[hello,world]
hello
world
编辑:上面的代码比需要的复杂得多。真的,所有你想要的是
class TakeArgs a其中
takeArgs :: a - > [字符串] - > IO()
实例TakeArgs(IO())其中
takeArgs a _ = a
实例TakeArgs a => TakeArgs(String - > a)其中
takeArgs f(x:xs)= takeArgs(fx)xs
takeArgs f [] =错误列表结尾
In this response to another question, a little Haskell code sketch was given which uses wrapper functions to factor out some code for doing syntax checking on command line arguments. Here's the part of the code which I'm trying to simplify:
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
Is there a way (maybe using Template Haskell?) to avoid having to write extra functions for each number of arguments? Ideally, I'd like to be able to write something like (I'm making this syntax up)
generateArgumentWrapper<2, showUsageMessage>
And that expands to
\fn args -> case args of
[a, b] -> fn a b
_ -> showUsageMessage
Ideally, I could even have a variable number of arguments to the generateArgumentWrapper
meta-function, so that I could do
generateArgumentWrapper<2, asInt, asFilePath, showUsageMessage>
And that expands to
\fn args -> case args of
[a, b] -> fn (asInt a) (asFilePath b)
_ -> showUsageMessage
Is anybody aware of a way to achieve this? It would be a really easy way to bind command line arguments ([String]
) to arbitrary functions. Or is there maybe a totally different, better approach?
Haskell has polyvariadic functions. Imagine you had a type like
data Act = Run (String -> Act) | Res (IO ())
with some functions to do what you want
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"
now, all you you need is to marshal functions into this Act
type. You need some extensions
{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
and then you can define
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 "")
now you can define
takeNArgs n act = takeNargs' n (makeAct act)
which makes it easier to define your original functions
takesSingleArg :: (String -> IO ()) -> [String] -> IO ()
takesSingleArg = takeNArgs 1
takesTwoArgs :: (String -> String -> IO ()) -> [String] -> IO ()
takesTwoArgs = takeNArgs 2
But we can do even better
takeTheRightNumArgs f = takeNArgs (numberOfArgs f) f
Amazingly, this works (GHCI)
*Main> takeTheRightNumArgs putStrLn ["hello","world"]
hello
*Main> takeTheRightNumArgs (\x y -> putStrLn x >> putStrLn y) ["hello","world"]
hello
world
Edit: The code above is much more complicated than it needs to be. Really, all you want is
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"
这篇关于如何避免为执行模式匹配的函数编写样板代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!