forループも代入もないHaskellでどうやってコマンドラインオプションを扱おう。きっと再帰なんだろうなぁ、再帰しか書けないし、しょうがない再帰だ。
shuffle_H.hs において
data OptionParam = OptionParam { count :: Int, src :: String, dst :: String }
OptionParam型はメンバに個数・元・先を持つ。
getOption :: [String] -> OptionParam -> OptionParam
getOption関数は文字列のリストとOptionParamを受け取ってOptionParamを返す。
getOption [] option = option getOption (p : []) option = option getOption (p : v : args) option | p == "--count" || p == "-c" = getOption args option { count = read v } | p == "--src" || p == "-s" || p == "--input" || p == "-i" = getOption args option { src = v } | p == "--dst" || p == "-d" || p == "--output" || p == "-o" = getOption args option { dst = v } | otherwise = getOption (v:args) option
実装は引数のパターンで分ける。概ね option { count = read v } というような、既存のOptionParamの一部を変更して新しいOptionParamを返すという機能に依存している。やりたいことは結局代入。猛烈に回りくどい代入。ツンデレが半端ない。
ガードを使ってるが、別にif文でもかまわない。ただ、Haskellの貧弱なifの構文ではネストがどんどん深くなるのが嫌なだけである。PythonのelifやCのelse ifがあれば迷いなく使ってる。caseでないのはここの判定はパターンでなく真偽値だから。関数引数パターンとcaseはパターンだがifとガードは真偽値。工夫すればどちらでも表現できるのかもしれないが、得意分野は違う。パターンの場合"--count"と"-c"をorで結ぶなんてことは表現できない。Scalaでは表現できるのでHaskellのcaseが貧弱なのは間違いない。
case class OptionParam(count: Int, src: String, dst: String) def getOption(args: List[String], option: OptionParam): OptionParam = { args match { case p :: v :: axs => p match { case "--count" | "-c" => getOption(axs, option.copy(count = v.toInt)) case "--src" | "-s" | "--input" | "-i" => getOption(axs, option.copy(src = v)) case "--dst" | "-d" | "--output" | "-o" => getOption(axs, option.copy(dst = v)) case _ => getOption(v :: axs, option) } case _ => option } }
Scalaのmatch case文にはorが書ける。書けると気付いた時は驚いた。
Haskellの option { count = read v } 相当の記述が option.copy(count = v.toInt) だが、copyメソッドはcase classのデフォルトの機能だ。後発だけあって、関数型言語風にするのに求められるナニガシかをきっちり仕様にしている。でもLISP以上に手続き型言語寄りだ。Scalaを関数型言語として使うのは相当な自制心が求められる。
class OptionParam: def __init__(self): self.count = 50 self.src = u'.' self.dst = u'shuffle.m3u' self.getOption(sys.argv[1 :]) def getOption(self, args): if len(args) < 2: return self p = args[0] v = args[1] if p == '--count' or p == '-c': self.count = int(v) elif p == '--src' or p == '-s' or p == '--input' or p == '-i': self.src = v.decode(defaultEncodeing) elif p == '--dst' or p == '-d' or p == '--output' or p == '-o': self.dst = v.decode(defaultEncodeing) else: return self.getOption(args[1 :]) return self.getOption(args[2 :])
Pythonでも同じように再帰で書いてみた。再帰は関数型言語の専売特許でもなんでもない。単に非効率だったりスタックが心配だからループに展開するのが普通なだけだ。本来こんな処理はループで書く。再代入は書かないようにと思っていたが、Scalaのcopyメソッドのようなものがなかったので、再帰だけど再代入している。再代入してました、参ったな。自分で決めた縛りをイキナリ破ってる。
2014.07.17
getOption :: [String] -> OptionParam -> OptionParam getOption [] option = option getOption (p : []) option = option getOption (p : v : args) option = if p == "--count" || p == "-c" then getOption args option { count = read v } else if p == "--src" || p == "-s" || p == "--input" || p == "-i" then getOption args option { src = v } else if p == "--dst" || p == "-d" || p == "--output" || p == "-o" then getOption args option { dst = v } else getOption (v:args) option
2014.07.30
OSTRACISM CO.
OSTRA / Takeshi Yoneki