scalaの復習
scalaをかなり久々に使ってみた。文法をすっかり忘れていてReferenceのPDF(のBNF)見ながら書きました。
LISPを作ろうとしたけど、そこまでいたらずパーズしてbuiltin関数を呼べる程度(lambdaもmacroも無い)。
// scalac lisp.scala // scala lisp.Main package lisp import scala.util.matching.Regex import scala.util.parsing.combinator._ object Main extends JavaTokenParsers { abstract class Expr case class Sym(symbol: Symbol) extends Expr case class Str(string: String) extends Expr case class Num(number: Long) extends Expr case class Lst(list: List[Expr]) extends Expr def symbol: Parser[Expr] = new Regex("""[-a-zA-Z0-9+*/%._]+""") ^^ ((token: String) => Sym(Symbol(token))) def string: Parser[Expr] = stringLiteral ^^ ((token: String) => Str(lit2str(token))) def numeric: Parser[Expr] = decimalNumber ^^ ((token: String) => Num(token.toLong)) def list: Parser[Lst] = "(" ~> exp.* <~ ")" ^^ ((list: List[Expr]) => Lst(list)) def exp = string | numeric | symbol | list def lit2str(lit: String) = { val escape: (Char => Char) = { case 'r' => '\r' case 'n' => '\n' case 't' => '\t' case other => other } val (ret, _) = lit.substring(1, lit.size - 1).foldLeft(("", false)) { case ((buf, true), cur) => (buf + escape(cur), false) case ((buf, false), '\\') => (buf, true) case ((buf, false), cur) => (buf + cur, false) } ret } type Env = Map[Symbol, Any] def eval(env: Env)(expr: Expr): Any = expr match { case Sym(symbol) => env(symbol) case Str(string) => string case Num(number) => number case Lst(list) => { val vals: Any = list.map { eval(env)(_) } //println(vals) vals match { case () => () case func :: args => func.asInstanceOf[List[Any] => Any].apply(args) } } } val env: Env = Map( Symbol("+") -> ((args: List[Any]) => args.foldLeft(0L)( (a, b) => a + b.asInstanceOf[Long])), Symbol("p") -> ((args: List[Any]) => { args.foreach(print); println }) ) def main(args:Array[String]) { val list = parse(exp, """(p "10 + 20 = " (+ 10 20))""") //val list = parse(exp, """(+ 10 20)""") println(list.get) println(eval(env)(list.get)) } }
注目点
- { case PATTERN => ... }でのクロージャが(arg) => arg match { case PATTERN => ...}の省略形である
- func{...} は func({...})であり、func{...}.xxxはfunc({...}).xxxである
- ぱっとみの誤解を避けるために括弧をつけたほうがよさそうだ
- 戻り値が無い(Unit型, (), Javaでのvoid)関数は、=なしでかける。この場合、ブロック式の値が何でも、戻り値Unitになる
- 部分適用 func(a)(_)はdef foo(a)(b)のように定義しなくてはいけない
- str.foreach(print)とかける。foreachが関数型をうけとるよう定義されてるからっぽい。def foo(arg:Any)だとfoo(print)とは書けない。foo(print _)のように書く必要がある(またはfoo(print: (Any => Unit)))
- Mapの引数の a -> bはタプル(a, b)の別記法
ポジティブな感想
- シンボル
- パターンマッチ
- 関数型プログラミングもできる点
- Javaよりはデフォルトネームスペースで提供する機能が多いとこ(ListやMapなど)
- パーザー関係機能が標準にあること
- (メソッド呼び出しを二項演算子風にかけるとこ)
- (XML構文があるとこ)
ネガティブな感想
- groovyのほうがいいと感じる点も結構ある
- タプルが便利であるが、それを使うと括弧が多くなりがちに
- 動的言語と比べてしまうと、リフレクションが弱い
- 若干混乱する仕様
最初のlit2str
def lit2str(lit: String) = lit.substring(1, lit.size - 1).foldLeft(("", false)) { case ((buf, esc), cur) => { if (esc) (cur match { case 'r' => buf + '\r' case 'n' => buf + '\n' case 't' => buf + '\t' case _ => buf + cur }, false) else (if (cur == '\\') (buf, true) else (buf + cur, false)) } }._1
ここから上記にリファクタリングしました。
まず、buf + をmatchの外側に出します。そして esc分岐をパターン中に埋め込み、trueのときのescape部分は外に関数として出し、falseのときのバックスラッシュ処理はパターンに埋め込む、という感じを経て上のコードになりました。
関数型だと、ボトムアップで組み立てがちで、探りつつ式をその場で入れ替えながらやってしまうことで、後から見ると複雑になってしまう。でもこうしたちょっとの入れ替えだけでだいぶ見易さが変わるのも、関数型のいいところでしょうか(逆にちょっとの入れ替えで大きく見にくくなるともいえてしまうけど)。