kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Nim勉強日誌(1) Hello, Parser Combinator!

そろそろ新しい言語に触れないとなあということでこれまでちらっと見たことがあったプログラミング言語Nim

index - Nim Programming Language

を触ってみることに決定。(1)とついていますが、これで飽きてやめるかもしれませんが悪しからず。

  • Python風味のインデント文法
  • マクロ
  • 演算子オーバーローディング
  • 実行速度を上げるためのいろいろな工夫

とかがあるみたいです。自分にとってのHello, World!はパーザコンビネータを作成することだと以前書いたことでもあるし、さっさとパーザコンビネータを作ってみました。

gist.github.com

つまづいたポイントをいくつか:

  • GenericsC++のtemplateの様に展開されてから型チェックが行われるので、定義したあとに呼び出して初めて型エラーがわかることが多い
  • 謎のinternal errorがしばしば発生する
  • 無名関数(Anonymous Proc)の型を表記する構文がしんどい: proc(arg: type): type のように書かなければならず、しかも、型であるにもかかわらず仮引数名はどうやら省略できないらしい。
  • Anonymous Proc自体の構文もしんどい。無名関数の返り値だけでなく引数の型もすべて表記しなければいけないため、複雑な型を受け取る無名関数を書こうとするとかなり面倒くさい。
  • インデントすべきところとしないで良いところの区別が不明瞭
    • 一行に押しこもうとすると、複雑な行はインデントしてくださいという旨のコンパイルエラーが出る
  • タプルがあるのは良い
  • Genericsもおおむね素直に書ける

今のところの感想としては、内部DSLを書くのにはあまり向いていない言語だなあ…というところでしょうか。

PEGでa1 + a2 + ... + an = bを判定する

このネタは

parser.connpass.com

でToshihiro Kogaさんに教えてもらったもので、私のオリジナルではないのですが、ちょっとおもしろいので貼ってみます。

まず、非負整数nについて、1の出現数によってエンコードします。

0 = 
1 = 1
2 = 11
3 = 111
4 = 1111
...

といった具合です。このようにエンコードされた非負整数について、

a_1 + a_2 + ... a_n = b

が真であるかどうかを通常のPEGで判定できるというお話です。

S <- A !.
A <- "1" A "1" / "+" A / "=";

このようなPEGにおいて、S

  • =: 0 = 0
  • 1+1=11: 1 + 1 = 2
  • 11+1=111: 2 + 1 = 3
  • 11+11+11=111111: 2 + 2 + 2 = 6

という文字列を受理しますが、

  • 11+1=1: 2 + 1 = 1

という文字列を受理しません。とてもシンプルなPEGで(Macro PEGでなく)このような計算を表現できるのは少し面白いと思ったので、紹介してみました。

プログラミング言語の好き嫌いと人の好き嫌いは別物である

という至極当然のことを改めて言っておきたいです。

たとえば、自分は今はScalaをメイン言語として使っていて、Javaの色々な部分がイケてないなーと感じることは多々あり、時折ディスることもありますが、Javaユーザを見下したりしたことはないと思っていますし、Twitter上でもJavaメインで使っている人と仲良く(?)やり取りします。JavaScriptは、今のなんでもWebアプリを招いた元凶だとすら思っており最も好ましく思っていない言語のひとつですが、JavaScriptユーザに含むところは何らありません。等々

一方、好きな言語のユーザであっても、この人とは根本的に考えが相いれないなーとか、言動があまりにも攻撃的過ぎてまともにやり取りしたくない人も多いです。個人名はあえて出しませんが。自分の言動が攻撃的過ぎると感じる方ももちろんいらっしゃると思います(自戒を込めて)。

とりあえず書き残しておきたくなったので、ここに記す。

Klassic言語の開発状況

リテラル

  • Byte
  • Short
  • Int
  • Long
  • Float
  • Double
  • Boolean
  • Unit
  • String

演算

加減乗除、比較、論理演算、などなど。異なる数値型の暗黙変換は禁止(Int + LongなどはNG)

リスト

[ [1 2 3]
  [4 5 6]
  [7 8 9]
]

こんな風に、改行だけでなく空白がセパレータになることもできる(実験的文法)。

無名関数

val add = (x, y) => x + y

関数定義

def add(x, y) = x + y

while式

while(condition) expression

foreach式

  • 今のところiterator()メソッドを持っているオブジェクトすべてに適用できる
foreach(var in expression) expression

if式

if(condition) thenClause else elseClause

ヒアドキュメント

println("#{<<TAG1 + 1} #{<<TAG2 + 2} #{<<TAG3 + 3}")
tag1
TAG1
tag2
TAG2
tag3
TAG3

ヒア式

  • ヒアドキュメント実装の副産物として生まれた
println(<<$EXP1 * <<$EXP2); //(1 + 2) * (3 + 4) → 21
1 + 2
EXP1
3 + 4
EXP2

String Interpolation

  • ${}じゃなくて#{}な辺りはRuby風味。
val x = 10
val y = 20
println("x = #{x}, y = #{y}")
println("x + y = #{x + y}")

オブジェクト生成とかメソッド呼び出しとかJava FFIとか

val list = new java.util.ArrayList
list.add(1)
list.add(2)
println(list)
val buffer = new java.lang.StringBuffer
buffer.append("A").append("B").append("C")
println(buffer)
println([
  "FOO"
  "BAR"
  "BAZ"
])
println("FOO".substring(0, 1))

型チェック

  • 実装始めたばっかり
  • ごく基本的な型チェックが動く
{ val i = 1;
  i } // 型はInt
val a = 1
val b: Long = a //NG。IntからLongへの暗黙変換は許されない
b

クラスシステム(Klass System)

  • 未実装。
  • サブタイピングはオプショナルにしたい

ほとんどメモ書きですが…。

プログラミング言語基礎勉強会で発表してきます

明日(6/11(土))に、こういう勉強会があるのですが、

xbase.connpass.com

きょんさんから、出演依頼があったのと、これからあちこちでなんか発表したいなーという気分があったので発表することにしました。

タイトルは「私のプログラミング言語の学び方」です。ざっくり言うと、既存の、プログラミング言語の学び方に違和感があるので、その辺を言語化してみた、というような話です。だいたい、プログラミング言語の学び方というと

  • 構文の丸暗記
  • とにかくサンプルプログラムを手を動かして打ち込んでいく

という、どうも両極端アプローチがやたら目につく気がするんですが、そういうのって、1言語習得辺りの効率が悪いと思うので、もっと統一的な理解をするのがよいのでは、というやや強まった発表(の予定)です。

kmizu.hatenablog.com

らへんの話をもうちょいふくらました感じというか。

というわけで、明日はよろしくおねがいします。

斜め読み論文紹介(1)「From APIs to Languages: Generalising Method Names」

タイトルは、斜め読み論文紹介ですが、

togetter.com

↑の辺の企画の亜種だと思ってください。気が向いたらときどき書いていこうと思います。第一回は、From APIs to Languages: Generalising Method Namesです*1

唐突ですが、Smalltalkという言語があります。オブジェクト指向を発明したとかいう言語ですが(テキトーな言い方だ…)ここでは全く関係がないのでおいておきます。Smalltalkの系列の言語は、メソッド名の構成に関してやや特殊なところがあります。それは、メソッド名をいくつかの断片に分けて記述するところです。

たとえば、辞書に値を突っ込むには、次のようにします:

kmizu := Dictionary new.
kmizu at:'Age' put:'32'.
kmizu at:'Sex' put:'Male'.
kmizu at:'Name' put:'Kota Mizushima'

ここで、at:put:で合わせて一つのメソッド名を構成しているのが、Smalltalk系列(Objective-Cも含む)のメソッドの特異なところです。これは、いわゆる名前付き引数とも違っていて、通常、メソッドの断片の順序は変更できません(もし最近のSmalltallkだとできるとかあれば教えてください)。

ここまでが準備です。さて、このように、メソッドをメソッド名の断片に分けられるのなら、その断片について

  • 二つの断片をくっつけたり
  • 二つの断片の片方だけOKだったり(|)
  • 繰り返したり(0回以上または1回以上)(*,+)
  • オプショナルだったり(?)

といった規則を使って、断片同士くっつけられるはず!、というのがこの論文のポイントです。メソッド名の断片を基本単位とした、正規表現のようなものが書けるようになった、という感じでしょうか。このような機構を指して、著者らは「Generalized Method Names」と呼んでいます。以下は、論文中に出てくるサンプルコードです。

method use(x) +addRatio(y1, y2) ?multiplyBy(z) {
  var value := x
  for (y1) and (y2) do { a, b −>
    value := value + a / b
  }
  for (z) do { v −> 
    return value * v 
  }
  return value
}
use(0) addRatio(1, 2) addRatio(2, 4)
       multiplyBy(6) // −> 6
use(1) addRatio(2, 3)
       multiplyBy(2) // −> 3 1/3

useで初期値を設定して、addRatio有理数を足しこんで、multiplyByで掛ける、という具合です。ポイントは、addRatioにはprefixとして+が付いており、useに続いて、addRatioを1回以上繰り返して呼び出せること、それに続いて、multiplyByを0回または1回呼び出せることです。

このGeneralized Method Namesを使うことで、

  • if-elseifのような制御構造
  • パターンマッチ
  • SQLライクなクエリ言語
  • try-catch-finallyのような例外処理機構
  • テスト記述言語

といったものをすべて統一的な機構で扱えるのです(というのが著者らの主張です)。

さらに、これらの、メソッドの断片について、部分型(Subtype)関係が定義されているのが面白いところです。たとえば、

a() *(b() | c()) <: a() *b() *c()

というサブタイプ関係が成り立つそうです。結構ややこしいので詳細は論文を読んでもらうことにします(というか、自分もサブタイプ関係のところはちゃんと読んでいない)。

ともあれ、メソッド名の断片を基本単位としてなんかするというのは、結構昔から聞いたことがあるテーマではありますが、結構綺麗に一般化できるのだなーというのが素直な感想です。

あと、ちょっと面白いのは、メソッド断片に対するマッチングはgreedyに行われるので、たとえば、

method a(x) *b(y) b(z) { ... }

のような、決してマッチすることのない(二つ目のb(y)で全部`bの出現を食べてしまうため)メソッド定義もできちゃうところですね。

テストDSLの例みると、

method scenario(desc)
  ?(Given(context) *And(contexts))
  When(event)
  Then(expr) ShouldBe(val)
  *(And(exprs) ShouldBe(vals)) {...}

method feature(name)
  ?background(initialisation)
  +(scenario(desc)
  ?(Given(context) *And(contexts))
  When(event)
  Then(expr) ShouldBe(val)
  *(And(exprs) ShouldBe(vals)))

のような、超メソッド解決がややこしそうな例がありますが、これはまあ柔軟性の代償というところでしょうか。というわけで、なんとなく数日前に読んだ論文を紹介してみました。他の方々もなんかおもしろんぶんがあれば紹介してもらえると嬉しいかもです。

*1:Proceedings of the 11th Symposium on Dynamic Languages 2015。自分はそれほど詳しいわけではないですが、動的型付き言語関係の国際会議のようです

お役立ち中置パターン in Scala

Scalaには中置パターン(infix pattern)と呼ばれる機能があります。これは単純にいうと、

case class ~[A, B](a: A, b: B)

のようにして定義したケースクラスに対して*1

scala> val ab = new ~(1, "FOO")
ab: ~[Int,String] = ~(1,FOO)

scala> val a ~ b = ab
a: Int = 1
b: String = FOO

このようにパターン名を中置(infix)にして書くことができる機能です。この機能、一見使い道が少なそうですが、実は皆さん、日頃からよく使われています。

まず、

list match {
  case x::y::xs =>
  ...
}

のように、リストを::を使ったパターンマッチで分解することはよくあると思いますが、これは、::という中置パターンがあればこそできる書き方です。これができないと、

list match {
  case ::(x, ::(y, xs)) => 
}

のように、いちいちネストした形式でパターンを書く必要があり、これは書きやすくも読みやすくもありません。

また、Scalaのパーザコンビネータを使ったとき、

"(" ~ expression ~ ")" ^^ { case _ ~ e ~ _ =>
  ...
}

のようにして、マッチした式の値のうち一部だけを取り出しますが、これも~を中置パターンとして使えているからこそです。これがないと、

"(" ~ expression ~ ")" ^^ { case ~(~(_, e), _) =>
  ...
}

のようにネストした形でパターンを記述する必要があり、とてもuglyです。~でつながれる要素数がさらに増えるともはや書き下すことが難しくすらなります。

というわけで、中置パターンは便利です、という話でした。

*1:実際には、unapplyが定義されていればいいわけですが、説明の簡略化のためその辺はばっさり略