#なコマンドライン解析

予約に失敗してD言語ハッカソン行けなかったので移植はコンパイルを通した状態で断念しました。
後しばらくは、Dの集まりもないだろう。

やっぱりSML#だなということで、SML#に黙々と移植をしてました。
いちお、作っていたテストは全部通って、OpenGLを動かそうとして、
動かなくて、テスト足らないよ俺ってかんじなんだけどまぁ、直して動きました!
おお!俺すげぇ。もう、コンパイラうざいくらいエラーを出してくれるので
Dよりも移植後の問題は少なかったのかもしれません。

でも、最初はセグフォったぞ。って調べたら無限ループしてた。それは死ぬはな。
ということで、SMLはバグってないけど、自分の頭が悪いとやっぱりバグるという話なのでした。

それで、コマンドラインオプションを作っていたわけですが、なにげにめんどくさいし美しくない気がするんです。
ライブラリとか使えば良いのかなぁとかDとかOcamlとか見てたわけですが、関数型言語だと
どうなるのかなといろいろ書いてみてたわけです。とりあえず、再帰で書いて、末尾再帰最適化して、とかとか。
で#使えるってことに気がついたので説明します。

SML#の#演算子は素晴らしいです。
ISWIMのSECDマシンは奇麗だったけど、それよりさらに一歩上を行っている。
それが#演算子なのダァ!!
ということで、以下にコマンドラインオプションを美しく解析するプログラムです。

  type Opts = {
      files: string list,
      out: string,
      framework: string,
      link: bool,
      s: bool,
      run: bool,
      p: bool,
      debug: bool
  }

  fun opts(args: string list, m:Opts) :Opts = (
    case args of
        "-c"::xs => opts(xs, m # {link = false})
      | "-S"::xs => opts(xs, m # {link = false, s = false})
      | "-run"::xs => opts(xs, m # {run = true})
      | "-d"::xs => opts(xs, m # {debug = true})
      | "-o"::out::xs => opts(xs, m # {out = out})
      | "-p"::xs => opts(xs, m # {p = true})
      | "-framework"::f::xs => opts(xs, m # {framework = #framework m ^ " -framework " ^ f})
      | n::xs => opts(xs, m # {files = n :: #files m})
      | [] => m
  )

    val opt = opts(CommandLine.arguments(), {files=[], out="", link=true, s=false, run=false, p=false, debug=false,framework=""})

まず、オプション解析結果レコードを用意しておき、optsにコマンドラインのオプションとデフォルト値を設定した
レコードを入力として呼び出します。

で、オプションがあったら、SECDマシンを書くが如く、状態を遷移させます。
SECDマシン的に書くと、遷移させる状態が8個くらいになってFOFLSRPDマシンみたいなことになり、
何個目が何かは分からなくなり、長いよ、分かり辛いよ美しくないよ!ってなってしまいます。

(* before わけのわからない、遷移*)
        "-c"::xs => opts(xs, files, out, framework, false, s, run, p, debug)
      | "-S"::xs => opts(xs, files, out, framework, false, false, run, p, debug)

#演算子を使う事で、

(* after *)
        "-c"::xs => opts(xs, m # {link = false})
      | "-S"::xs => opts(xs, m # {link = false, s = false})

と変更したい状態だけ書けば良くなるわけです!!!
名前を使う事でfalseって何の状態だっけ?っていうわけの分からなさも解消されています。
なんて素晴らしいんだ。#って、そういう事だったのか!!!

うお、#演算子Scalaにも欲しいぜっ!ってことになる訳ですが、Scala2.8以降のcase classのcopyがそれに当たります。

  case class Opts(
      files:List[String]= List(),
      out: String = null,
      framework: String = "",
      link: Boolean = true,
      s: Boolean = false,
      run: Boolean = false,
      p: Boolean = false,
      debug: Boolean = false
  )

  import scala.annotation.tailrec
  @tailrec
  def opts(args:List[String], m:Opts=Opts()) :Opts = {
    args match {
      case "-c"::xs => opts(xs, m.copy(link = false))
      case "-S"::xs => opts(xs, m.copy(link = false, s = false))
      case "-run"::xs => opts(xs, m.copy(run = true))
      case "-d"::xs => opts(xs, m.copy(debug = true))
      case "-o"::o::xs => opts(xs, m.copy(out = o))
      case "-p"::xs => opts(xs, m.copy(p = true))
      case "-framework"::f::xs => opts(xs, m.copy(framework = m.framework + " -framework "+f))
      case n::xs => opts(xs, m.copy(files = n::m.files))
      case List() => m
    }
  }
  def main(args:Array[String]) {
    val opt = opts(args.toList)
  }

Scalaだとこんな感じ。な、#だろ
Scalaだと、さらに、デフォルト引数を指定出来るのでこんな感じに書けました。

ということで、#いいよ、copy使えるよってことで、ガシガシ使って行く事にしたのでした。
他に使えるところは、コンパイラの型だけ書き換えたい場合とかに、他の要素はそのままで、要素が何行目だとか
どうでも良い情報も#でコピってくれるのでよいです。