我该如何将选项记录转换为字符串列表?

时间:2015-05-22 18:23:08

标签: haskell

我有一个选项记录,我需要转换成命令行参数。

例如:

data Options = Options { optFoo :: Maybe Int
                       , optYes :: Bool
                       , optBar :: Maybe String
                       }

options = Options { optFoo = Just 3
                  , optYes = True
                  , optBar = Nothing
                  }

callThing :: Options -> IO ()
callThing opts = do
    callProcess "/usr/bin/thing" $ optsToArgs opts

-- Output should be: ["--foo", "3", "-y"]
optsToArgs :: Options -> [String]
optsToArgs opts = ???

我想象能够使用List Monad,但我无法弄清楚如何让它工作。

在我的特定情况下,Options中有大约20种不同的东西,因此使用嵌套的if / case语句的解决方案并不理想。

是否有解决此类问题的常见模式?

2 个答案:

答案 0 :(得分:5)

不,据我所知,没有真正的内置方法可以做到这一点。您可以尝试使用某种通用编程,但这可能不会很好地工作并且非常困难。相反,我建议重新构建Options类型:

data Option
    = Foo Int
    | Yes
    | Bar String
    deriving (Eq, Show)

type Options = [Option]

optToArgs :: Option -> [String]
optToArgs opt = case opt of
    Foo n -> ["--foo", show n]
    Yes   -> ["-y"]
    Bar s -> ["--bar", s]

optsToArgs :: Options -> [String]
optsToArgs = concatMap optToArgs

然后你会

> optsToArgs [Foo 3, Yes]
["--foo", "3", "-y"]
> optsToArgs [Yes, Bar "test"]
["-y", "--bar", "test"]

这样,每个选项都有不同的构造函数。从单个选项到其相应参数的转换在一个地方处理,然后将多个选项转换为参数列表很简单。如果需要,这还可以让您拥有更多层次结构的选项结构:

data FooOption
    = Foo1 Int
    | Foo2 Double

data BarOption
    = Bar1 String
    = Bar2 (String, String)

class Opt o where
    toArgs :: o -> [String]

instance Opt FooOption where
    toArgs (Foo1 n) = ["--foo", show n]
    toArgs (Foo2 d) = ["--foo", show d]

instance Opt BarOption where
    toArgs (Bar1 s) = ["--bar", s]
    toArgs (Bar2 (s1, s2)) = ["--bar", s1, "--bar", s2]

data Option
    = Foo FooOption
    | Bar BarOption
    | Yes

instance Opt Option where
    toArgs (Foo f) = toArgs f
    toArgs (Bar b) = toArgs b
    toArgs Yes     = ["-y"]

type Options = [Option]

instance Opt o => Opt [o] where
    toArgs = concatMap toArgs

然后你就有了

callThing = callProcess "/usr/bin/thing" . toArgs

举个例子:

> toArgs [Bar $ Bar2 ("hello", "world"), Foo $ Foo1 3, Yes]
["--bar", "hello", "--bar", "world", "--foo", "3", "-y"]

答案 1 :(得分:1)

我会创建一个TypeClass,它将选项的名称作为参数。

class ToCommand a where
  toCmd :: a -> String -> [String]

instance ToCommand Int where
  toCmd i n = [n,show i]

instance ToCommand Bool where
  toCmd True n = [n]
  toCmd False _ = []

instance (ToCommand a)=> ToCommand (Maybe a) where
  toCmd Nothing _ = []
  toCmd (Just a) n = toCmd a n

使用RecordWildcards,你可以做到

 optToArgs opts{..} = concat (toCmd optFoo "--foo") (toCmd optYes "-y") ...

也许更通用的解决方案可行,但实施起来会更复杂。