mathhunの日記

Haskellと機械学習の勉強日記。PRML読みます。

HaskellのOptparse-Applicativeの底力を知る

元ネタ RubyのOptionParserの底力を知る - ザリガニが見ていた...。

Haskell版で苦労最小限でオプションを解析できるのか、調べてみた。

使うライブラリは optparse-applicative
理由は最近見かけたこのスライドで推薦されていたから
Getting it Done with Haskell - Google スライド

optparse-applicative のバージョンは 0.9.1.1
githubの最新版ではいくつかの関数・APIが変更されているようで、使用バージョンではマニュアル通りに書いてもエラーになるものもあった。

基本

module Main where

import Data.Monoid
import Options.Applicative

data MyOption= MyOption { optName :: String }

runWithOptions :: MyOption -> IO ()
runWithOptions opts =
  putStrLn $ "name = " ++ optName opts

main :: IO ()
main = execParser opts >>= runWithOptions
  where
    parser = MyOption <$> argument str (metavar "NAME")
    opts = info parser mempty
  • オプション定義のデータ型を定義する
  • ハイフン付きのオプションだけでなく素の引数も定義できる
  • 簡単なヘルプが自動で出力される
$ runghc ex01.hs foo
name = foo
$ runghc ex01.hs
Usage: ex01.hs NAME

1文字オプション

  • 1文字オプションは、-で始まる1文字のオプション名。
  • short で設定する
module Main where

import Options.Applicative

data MyOption= MyOption { optName :: String
                        , a :: Bool
                        , b :: String
                        , c :: Maybe String
                        } deriving Show

run :: MyOption -> IO ()
run opts = print opts

parser = MyOption <$> argument str (metavar "NAME")
                  <*> switch              (short 'a' <> help "1文字オプション 引数なし")
                  <*> strOption           (short 'b' <> help "1文字オプション 引数あり (必須)")
                  <*> optional (strOption (short 'c' <> help "1文字オプション 引数あり (省略可能)"))

main :: IO ()
main = execParser opts >>= run
  where opts = info parser idm

1文字オプションとロングオプション

  • ロングオプションは、--で始まる2文字以上のオプション名。
module Main where

import Options.Applicative

data MyOption= MyOption { optName :: String
                        , a :: Bool
                        , b :: String
                        , c :: Maybe String
                        } deriving Show

run :: MyOption -> IO ()
run opts = print opts

parser = MyOption <$> argument str (metavar "NAME")
                  <*> switch              (short 'a' <> long "along" <> help "1文字オプション 引数なし")
                  <*> strOption           (short 'b' <> long "blong" <> help "1文字オプション 引数あり (必須)")
                  <*> optional (strOption (short 'c' <> long "clong" <> help "1文字オプション 引数あり (省略可能)"))

main :: IO ()
main = execParser opts >>= run
  where opts = info parser idm

noオプション

  • noオプションにあたるものは無さそう

オプション引数を数値や配列として受け取る

  • ハイフン指定無しの文字列引数は argument str
  • 文字列引数は strOption
  • 整数値などは option
  • 文字列リストは many . strOption
  • 整数リストはいくつか試してみたが方法がわからず
module Main where

import Options.Applicative

data MyOption= MyOption { optName :: String
                        , optStr  :: String
                        , optStr2 :: String
                        , optInt  :: Int
                        , optDouble :: Double
                        , optStrList :: [String]
                        -- , optIntList :: [Int]
                        } deriving Show

run :: MyOption -> IO ()
run opts = print opts

parser = MyOption <$> argument str       (metavar "NAME")
                  <*> strOption          (short 's' <> long "string"   <> help "string")
                  <*> option             (short 'x' <> long "string2" <> help "string2")
                  <*> option             (short 'i' <> long "int"      <> help "int")
                  <*> option             (short 'd' <> long "double"   <> help "double")
                  <*> (many . strOption) (short 'S' <> long "strlist"  <> help "string list")
                  -- <*> (many . option)    (short 'I' <> long "intlist"  <> help "int list")

main :: IO ()
main = execParser opts >>= run
  where opts = info parser idm

オプション引数の選択肢

TODO

オプション引数のパターン指定

TODO

ヘルプ表示をデザインする

TODO

その他の関数

このあたりを読めば他に使えそうな関数が見つかる

Options.Applicative.Builder

また、gitコマンドのようにサブコマンドを定義する方法も用意されていて便利そう
optparse-applicative/Cabal.hs at master · pcapriotti/optparse-applicative · GitHub

マニュアル

自分の勉強のために書いてみましたが、公式マニュアルが綺麗にまとまっているのでそっちを読んだ方がいいですよ、というオチ。