Как создать вложенную/условную опцию с помощью optparse-applicative?

Можно создать выражение haskell, используя методы в optparse-applicative, которые анализируют программу такие варианты?

program [-a [-b]] ...

-a и -b — это необязательные флаги (реализованные с помощью switch) с ограничением, согласно которому параметр -b действителен только в том случае, если -a набирается раньше.

Спасибо


person The Linux Kitten    schedule 09.08.2014    source источник


Ответы (2)


Это возможно, с небольшими изменениями, двумя разными способами:

  1. Вы можете создать синтаксический анализатор, который разрешает -b только в том случае, если у вас есть -a, но вы не можете настаивать на том, чтобы -a стояло первым, поскольку комбинатор <*> в optparse-applicative не определяет порядок.
  2. Вы можете настаивать на том, чтобы опция -b следовала за опцией a, но вы делаете это, реализуя a как команду, поэтому вы теряете - перед ней.

Applicative определенно достаточно силен для этого, поскольку нет необходимости проверять значения, возвращаемые синтаксическими анализаторами, чтобы определить, разрешено ли -b, поэтому >>= не требуется; Если -a завершается успешно с любым выводом, -b разрешается.

Примеры

Я буду использовать тип данных, чтобы представить, какие аргументы присутствуют, но на самом деле они были бы более значимыми.

import Options.Applicative

data A = A (Maybe B)   deriving Show 
data B = B             deriving Show

Таким образом, опции нашей программы могут содержать букву А, которая может иметь букву В, и всегда содержать строку.

boption :: Parser (Maybe B)
boption = flag Nothing (Just B) (short 'b')

Способ 1: стандартные комбинаторы - -b может идти только с -a (любой порядок)

Я буду использовать flag' () (short 'a'), который просто настаивает на том, что -a существует, но затем использую *> вместо <*>, чтобы игнорировать возвращаемое значение () и просто возвращать то, что возвращает синтаксический анализатор boption, предоставляя опции -a [-b]. Затем я отмечу это A :: Maybe B -> A и, наконец, сделаю все это optional, так что у вас есть варианты [-a [-b]]

aoption :: Parser (Maybe A)
aoption = optional $ A <$> (flag' () (short 'a' ) *> boption)

main = execParser (info (helper <*> aoption) 
                        (fullDesc <> progDesc "-b is only valid with -a")) 
        >>= print

Обратите внимание, что поскольку <*> допускает любой порядок, мы можем поставить -a после -b (что не совсем то, о чем вы просили, но работает нормально и имеет смысл для некоторых приложений).

ghci> :main -a 
Just (A Nothing)
ghci> :main -a -b
Just (A (Just B))
ghci> :main -b -a
Just (A (Just B))
ghci> :main -b
Usage: <interactive> [-a] [-b]
  -b is only valid with -a
*** Exception: ExitFailure 1

Способ 2: подпарсер команды - -b может следовать только за a

Вы можете использовать command для создания subparser, который действителен только при наличии командной строки. Вы можете использовать его для обработки аргументов, как это делает Cabal, так что cabal install и cabal update имеют совершенно разные параметры. Поскольку command принимает аргумент ParserInfo, можно использовать любой синтаксический анализатор, который вы можете передать execParser, так что вы можете вкладывать команды сколь угодно глубоко. К сожалению, команды не могут начинаться с -, поэтому будет program [a [-b]] ... вместо program [-a [-b]] ....

acommand :: Parser A
acommand = subparser $ command "a" (info (A <$> (helper <*> boption)) 
                                         (progDesc "you can '-b' if you like with 'a'"))

main = execParser (info (helper <*> optional acommand) fullDesc) >>= print

Который работает следующим образом:

ghci> :main 
Nothing
ghci> :main a 
Just (A Nothing)
ghci> :main a -b
Just (A (Just B))
ghci> :main -b a
Usage: <interactive> [COMMAND]
*** Exception: ExitFailure 1

Поэтому перед -b нужно поставить a.

person AndrewC    schedule 10.08.2014

Боюсь, вы не можете. Это именно тот сценарий, с которым Applicative не может справиться в одиночку, а Monad может: изменение структуры последующих действий на основе более ранних результатов. В аппликативных вычислениях «форма» всегда должна быть известна заранее; это имеет некоторые преимущества (например, ускорение комбинаций массивов или выдача удобного читаемого экрана справки для параметров командной строки), но здесь он ограничивает вас анализом «плоских» параметров.

Интерфейс optparse-applicative также имеет Alternative, что позволяет проводить зависимый синтаксический анализ, хотя и другим способом, как показано AndrewC.

person leftaroundabout    schedule 10.08.2014
comment
Ты уверен? Мы действительно знаем эту структуру заранее, она просто вложенная. Не могли бы вы использовать command для a и использовать подпарсер для реализации b? - person AndrewC; 10.08.2014
comment
Это фиксировано, но не аппликативно. Вы можете включить некоторое статическое поведение ветвления, добавив тип, например f Bool -> (Bool -> f r) -> f r, то есть ограниченный тип (>>=). - person J. Abrahamson; 10.08.2014
comment
Спасибо @leftaroundabout. - person The Linux Kitten; 10.08.2014
comment
Извините, @leftaroundabout, но я не могу согласиться с вашими рассуждениями. Applicative достаточно силен для такого рода вещей, иначе мы не смогли бы использовать аппликативный интерфейс для парсека, чтобы позволить = появляться только после идентификатора. Вам не нужно проверять текст идентификатора, чтобы знать, что = может следовать за ним, поэтому вам не нужно привязывать. Точно так же вы можете разобрать -a и вернуть (), (не букву a). Вам не нужно проверять значение (), чтобы знать, что -b теперь разрешено, поэтому вам не нужно привязывать. Проблема заключается исключительно в том, что в этом контексте ‹*› не является детерминированным в отношении порядка. - person AndrewC; 11.08.2014
comment
@AndrewC: проблема в том, что реализация с использованием switch подразумевает, что нам нужно проверить логическое значение, чтобы узнать, разрешено ли -b; это то, что аппликативы не могут сделать, и в этом смысл моего ответа. Вы, безусловно, можете получить решение с помощью других приемов, как показано в вашем ответе, но на самом деле вы не используете аппликативный интерфейс, а встроенные функции более высокого порядка. Единственным простым и универсальным решением будет монадный (или, по крайней мере, стрелочный) интерфейс. - person leftaroundabout; 11.08.2014
comment
@leftaroundabout Я использую полностью стандартные комбинаторы Applicative & Alternative в своем первом методе, просто поверх необязательного флага '(optional взято из Alternative). Я понял, что реализовано с помощью переключателя, что означает, что в настоящее время реализовано с помощью переключателя. Можем ли мы договориться, боюсь, вы не сможете, если будете использовать switch? Причина, по которой я обеспокоен, заключается в том, что, как написано в настоящее время, подразумевается, что Applicative является чрезвычайно слабым интерфейсом для синтаксического анализа, а это не так. - person AndrewC; 11.08.2014
comment
Applicative является чрезвычайно слабым интерфейсом. Но да, Alternative достаточно мощный; честно говоря пропустил этот экземпляр. - person leftaroundabout; 11.08.2014