optparse-applicative与自定义monad

时间:2020-01-26 18:58:10

标签: haskell optparse-applicative

我正在尝试将自己的monad(而不是IO)与customExecParser https://hackage.haskell.org/package/optparse-applicative-0.15.1.0/docs/Options-Applicative-Extra.html#v:customExecParser一起使用。

所以我结束了(重要的功能是fff):

data MoscConfig = MoscConfig {
    datadir :: FilePath
  , config :: FilePath
  , pendingPath :: FilePath
  , socket :: FilePath
  }

type Mosco = StateT MoscConfig IO

main :: IO ()
main = join . customExecParser (prefs showHelpOnError) $
  info (helper <*> parser)
  (  fullDesc
  )

fff :: (a1 -> StateT MoscConfig IO a2) -> a1 -> IO a2
fff f  = (flip evalStateT (MoscConfig "" "" "" "")) . f

xyzz :: Text -> Mosco ()
xyzz x = do
  liftIO $ print x
  liftIO $ print "testabcxyz"

xyzz' :: Text -> Text -> Mosco ()
xyzz' x x' = do
  liftIO $ print x
  liftIO $ print x'
  liftIO $ print "testabcxyz"

parser :: Parser (IO ())
parser = do
  fff xyzz <$> textOption ( long "zzz" )
  <|>
  ((fmap fff) xyzz')
    <$> textOption ( long "zzz" )
    <*> textOption ( long "zzz" )

但是,上述方法的唯一缺点是需要fmap所需的次数(匹配xyzzxyzz中的函数参数)。我确实记得以前遇到过此类问题。有什么办法可以避免这种情况(只需要调用一个函数)?

理想情况下,我希望有一个monad转换器,但不幸的是,这似乎仅适用于IO

2 个答案:

答案 0 :(得分:2)

我认为这可以归结为一个问题:是否存在可以同时应用于以下两个方面的函数fff

xyzz  :: a -> r
xyzz' :: a -> b -> r

这样:

fff xyzz  :: a -> r'
fff xyzz' :: a -> b -> r'

答案是“否”,至少没有没有值得考虑的类型类欺骗手段。

相反,假设您的fff的实际版本除了与f无关,实际上没有任何作用,我想我会考虑写:

fff :: Parser (Mosco a) -> Parser (IO a)
fff = fmap $ flip evalStateT (MoscConfig "" "" "" "")

parser :: Parser (IO ())
parser = fff (xyzz  <$> textOption ( long "zzz" ))
     <|> fff (xyzz' <$> textOption ( long "zzz" ) <*> textOption ( long "zzz" ))

不过,整个方法似乎有些“偏离”。 解析选项时,您真的是否需要MoscConfig可用?除非您手上确实有一个非常复杂的选项解析问题,否则通常将选项直接解析为中间数据结构,然后针对该数据结构运行Mosco操作以修改MoscConfig声明并执行IO等。

答案 1 :(得分:0)

关于我要实现的目标(能够仅在Mosco monad上下文中传递参数以使其起作用-

moscparams ::
  Maybe Text
  -> Maybe Text
  -> Maybe Text
  -> Maybe Text
  -> Mosco a -> IO a
moscparams dd c pp sp x = do
  ddd <- crFile
  cd <- pure "not used"
  ppd <- crDirPending
  spd <- socketFile
  evalStateT x
    $ MoscConfig
      (maybe ddd cs dd)
      (maybe cd cs c)
      (maybe ppd cs pp)
      (maybe spd cs sp)

moscF' :: Text -> Text -> Mosco ()
moscF' x x' = do
  liftIO $ print x
  liftIO $ print x'
  liftIO $ print "testabcxyz"

moscparams' :: Parser (Mosco ()) -> Parser (IO ())
moscparams' x = moscparams
  <$> optional (textOption ( long "data-dir" ))
  <*> optional (textOption ( long "config-path" ))
  <*> optional (textOption ( long "pending-path" ))
  <*> optional (textOption ( long "socket-path" ))
  <*> x

parser :: Parser (IO ())
parser = do
  moscparams'
    (( moscF')
      <$> textOption ( long "example-param-1" )
      <*> textOption ( long "example-param-2" )
    )