简体   繁体   中英

optparse-applicative with custom monad

I'm trying to use my own monad (instead of IO ) with customExecParser https://hackage.haskell.org/package/optparse-applicative-0.15.1.0/docs/Options-Applicative-Extra.html#v:customExecParser .

So I've ended up with (significant function being 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" )

However, the only disadvantage with the above approach is needing to fmap the required number of times (matching the function arguments in xyzz or xyzz ). I do recall running into this type of problem before. Is there some way I can avoid this (and just have a single function needing to be called)?

Ideally I'd hope to have a monad transformer for this but unfortunately this seems to be implemented to IO only.

I think this boils down to the question: is there a function fff that can be applied to both of:

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

so that:

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

And the answer is "no", at least not without some type class trickery that isn't worth considering.

Instead, assuming your real version of fff doesn't actually do anything with f except compose with it, I guess I would consider writing:

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" ))

This whole approach seems a little "off", though. Do you really need a MoscConfig available while parsing options? Unless you have a really complicated options parsing problem on your hands, it would be more usual to parse the options directly into an intermediate data structure and then run your Mosco actions against that data structure to modify a MoscConfig state and do IO and so on.

In terms of what I wanted to achieve (being able to just pass parameters to function within the Mosco monad context -

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" )
    )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM