kmizuの日記

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

Amazon Prime Nowは確かに速かった

昨日、Amazon Prime Nowの存在を知り、送料を余計に支払えば1時間以内、無料枠でも2時間以内に注文した品を届けてくれるサービスであることを知った(該当エリアに住んでいることが条件。また、専用のPrime Nowアプリで注文する必要がある)。注文できる品物は、Amazon.co.jpで買えるものからすると限定されているが、この素早さは魅力的だ。というわけで、早速使ってみた。送料を余計に支払って1時間以内に届けてくれるやつで。

  • 21:00頃: 松屋で夕食を食べながら、Prime Nowアプリで注文
  • 21:30頃: まもなく到着との連絡がSMS経由で届く
  • もっとゆっくりかと思っていたので、自宅までダッシュ
  • 21:45頃: 無事に配達完了。Amazon.co.jpで注文したときと違って、梱包が簡素なのはいい。

とにかく、1時間以内にきっちりと配達してくれた。凄いサービスだ。

今更ES2015の勉強を始めてみた

タイトルの通りです。JavaScriptに関しては多少は知っていますが、知識が全然アップデートされていないので、この機会に再入門してみることにしました。以下はpackage.jsonなんかBabel関連で試行錯誤したので、結構変な構成になっているかも。

{
  "name": "es_copmbinator",
  "version": "0.0.1",
  "description": "A ES2015 Parser Combinator Library",
  "main": "index.js",
  "scripts": {
    "test": "ava -v"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/kmizu/es_copmbinator.git"
  },
  "keywords": [
    "parser"
  ],
  "author": "Kota Mizushima",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/kmizu/es_copmbinator/issues"
  },
  "homepage": "https://github.com/kmizu/es_copmbinator#readme",
  "devDependencies": {
    "ava": "^0.15.1",
    "babel-preset-es2015": "^6.9.0",
    "babelify": "^7.3.0"
  }
}

以下、遭遇した問題やES2015の機能について。

アロー関数

無名関数の簡略記法(というだけではないが)。本体が式である場合、returnが省略できるらしい。というか、ブロックは式ではないというのが正しいか。なんとも中途半端な仕様だが、これまでのJavaScriptの仕様と一貫性を保つためには仕方ないのか。

const add = (x, y) =>(x, y)
const printAdd = (x, y) => {
  util.log(x);
  util.log(y);
  return x + y; // return必須
};

クラス定義

割と普通にクラスを定義できる。暗黙のsuper();呼び出しがないことを知らなかった。もうちょっとわかりやすいエラーメッセージを出してほしい。ただ、メソッドの定義が、メソッド名(引数...) { ... }だけで書けるのは便利。

class ParseResult {
  constructor(next) {
    this.next = next;
  }
}

class ParseSuccess extends ParseResult {
  constructor(value, next) {
    super(next);
    this.value = value;
  }

  isSuccess() {
    return true;
  }
}

class ParseFailure extends ParseResult {
  constructor(next) {
    super(next);
  }

  isSuccess() {
    return false;
  }
}

letとconst

共にブロックスコープ。letは変更可能。constは変更不能。とりあえず、ほとんどconstしか使わなかった。

メソッドだけを取り出せない?

以下のようなことをしたかった

const combinator = new ParserCombinator;
const or = combinator.or;
or(new StringParser("FOO"), new StringParser("BAR"));

だが、thisがないと怒られてしまう。これはまあ、それまでのJavaScriptの仕様を考えると納得できるが、構文的にはthisが束縛されていて欲しいので残念。

trueと変数名とメソッド名と

const true = 1;

とすると怒られるのに、

class Hoge {
  true() {
  }
}
const hoge = new Hoge();
hoge.true();

は怒られない。

予約語 - JavaScript | MDN

に、実際に、「予約語は識別子のみに適用されます。(以下略)」

a.import
a["import"]
a = { import: "test" }

とあって、そういう仕様であることがわかる。

クラス中の暗黙のthis(thisの省略)がない

いやまあ、結局、これまでのJavaScriptを考えると仕方無いのだろうけど、

class OrParser extends Parser {
  constructor(lhs, rhs) {
    super();
    this.lhs = lhs;
    this.rhs = rhs;
  }
  parse(input) {
    const r1 = this.lhs.parse(input);
    if(r1.isSuccess()) {
      return r1;
    }else{
      return this.rhs.parse(input);
    }
  }
}

class OrParser extends Parser {
  constructor(lhs, rhs) {
    super();
    this.lhs = lhs;
    this.rhs = rhs;
  }
  parse(input) {
    const r1 = lhs.parse(input);
    if(r1.isSuccess()) {
      return r1;
    }else{
      return rhs.parse(input);
    }
  }
}

と書きたかった。

全体的に、ES2015の言語自体で、今のところそれほどハマったとは感じなかったというか、どっちかというと周辺環境の整備でハマった。

てな感じで生まれたのがこちら。

GitHub - kmizu/es_combinator: A Parser Combinator Library Written in ES2015. It is not intended to make practical library but to learn ES2015

以前に、このエントリで、新しい言語を覚えるためにはまずパーザコンビネータライブラリを作成すると書いていたが、まさにそのまんまというか。パーザコンビネータは、言語機能を多少は使えないと作れない類のものなので、やはり"Hello, World!"の代わりとして適しているとますます確信する次第です。

ところで、テスト中に書いた、四則演算式のパーザなのですが、もっと冗長になるかと思いきや、

  const E = () => A();
  const A = () => 
    c.f(() => M()).chainl(
      (
       c.s("+").map((op) => (lhs, rhs) => lhs + rhs)
      ).or(
       c.s("-").map((op) => (lhs, rhs) => lhs - rhs)
      )
    );
  const M = () => 
    c.f(() => P()).chainl(
      (
       c.s("*").map((op) => (lhs, rhs) => lhs * rhs)).or(
       c.s("/").map((op) => (lhs, rhs) => lhs / rhs)
      )
    );
  const P = () =>
    (c.s("(").cat(c.f(() => E())).cat(c.s(")"))).map((values) => {
      return values[0][1];
    }).or(c.f(() => N()));
  const N = () =>
    c.r("[0-9]+").map((n) => parseInt(n));

これだけで書けてしまったのが結構意外でした(ちゃんと数値の計算もしてるサンプル)。これは、アロー関数の本体が、式の場合はreturnを省略できるのと、chainlがやはり強力なコンビネータであって、これを実装すると、パーザを実装するのが格段に楽になるということかなと思います。ただ、規則に相当するものが全て関数でラップされている通り、遅延評価されるフィールドとか書けないので、若干不格好です。まあ仕方ない。

とまあこんな感じですが、ES2015は初心者もいいところだし、それ以前のJavaScriptもあまり得意ではないので、ツッコミどころがあるかもしれませんが、そこはツッコんでいただけるとありがたいです。

scala-nativeのサンプルプログラムをLinuxで動作させる場合の注意点

kmizu.hatenablog.com

についての補足的なエントリです。scala-nativeのサンプルプログラムsmallpt.scala__stderrpを使っていますが、Linux系には存在しないので、リンク時にこけてしまいます。

これを修正するには、smallpt.scalastdlib.scala__stderrp__stdoutpをそれぞれ、stderrstdoutに書き換えてやる必要があります。現状、条件コンパイルのような仕組みはサポートされていないのでこれをクロスプラットフォームで動作させる方法がたぶんないのがちと困ったものです(作者本人が作ったIssueには入っているので、そのうち解決されるだろうとは思いますが)。実は手元でunameを使ってOSによって分岐するコードを書いてみようとしたのですが、構造体のメモリレイアウトが微妙に違うのか、unameは呼び出せるものの、返り値に謎の値が入っているという事態になりました(なにか勘違いしているかも)。

scala-nativeでは相互末尾再帰呼び出しも最適化される

Scala Nativeなのかscala-nativeなのか迷ったのですが、今後はリポジトリ名のscala-nativeに統一しようかと思います。さて、タイトルについてですが、そのまんまです。

サンプルコードは以下:

gist.github.com

通常のJVM上でのScalaでは、末尾呼び出しが自分自身で(自己末尾再帰)、かつ、その関数(メソッド)がオーバーライドされない場合に限り、末尾呼び出しを除去することができましたが、scala-nativeはそれに限らず、一般の末尾呼び出しも除去(ジャンプに置き換えることができる)という話です。

上記サンプルコードでは、単純にやるとおよそ100000000個分スタックを積む必要がありますが、そのまま関数呼び出しとして実装するとスタックオーバーフローします。事実、最適化オプションを付けないclang(3.7)では同等のコードを実行しようとするとsegmentation faultを起こしました(-O1以上の最適化オプションを付けるとちゃんと実行できました)。

JVM上でのScalaでは末尾呼び出しの除去をやろうと思うと、非常に高いコストを払って自前でトランポリン形式にするくらいしか選択肢がなかったですが、scala-nativeではJVMという制約がないので、その辺の最適化もできるようになった、ということです。

追記: LLVMでは言語非依存の最適化として末尾呼び出しの除去が行えるらしいのでscala-nativeはその機能をそのまま使っているのかもしれませんね。

Scala Nativeを動かしてみた(1)

Scala Nativeはscalaのコードを(LLVMのIRを経由して)ネイティブコードにコンパイルするAOTコンパイラ(Ahead Of Time Compiler)です。その存在については、少し前にサイトができていたことで一部で話題になっていましたが、Scala Days 2016 NYCにて正式に公開されました。現在はPre-Release段階ですが、既にサンプルコードを試せるようになっていたので、環境を構築してみました(on Mac OS)。

$ git clone git@github.com:scala-native/scala-native.git --recursive

git submoduleとしてscala/scalaを持っているので、--recursiveを付けるのを忘れないようにしましょう。

  • llvm(clang)とbdw-gcをインストールする
$ brew install --with-clang llvm
...
$ brew install bdw-gc
$ sbt
> project rtlib
> publishLocal
> project nscplugin
> publishLocal
  • デモプロジェクトに移動(sbtを起動したまま)
> project demoNative
  • 実行(gc.hがないといったエラーが出るはず)
> run
  • bdw-gcにパスを通す
nativeClangOptions := Seq("-I/usr/local/Cellar/bdw-gc/7.4.2/include", "-L/usr/local/Cellar/bdw-gc/7.4.2/lib")

この、nativeClangOptionsというsettingKeyは、ここで定義されていて、要はclangに渡すオプションを格納するためのものです。インクルードパスとライブラリパスの両方を渡してやります。なお、ここでは、明示的にbdw-gcをインストールしたディレクトリにパスを通しましたが、うまくやればそもそもここでつまづく事はない…かもしれません。うまくいかない場合はこの辺をいじればなんとかなるということでもあります。

  • 再度実行
> run

なにやら色々warningが出ますが、それはともかく、赤枠で囲った部分が表示されればひとまず成功です。 f:id:kmizushima:20160512221647p:plain

Scala Nativeを試してみたいけど、うまく行っていない人が居るようだったので、とりあえず走り書き程度ですが、参考になればと思います。気が向いたら、サンプルプログラムをちょっといじってなんかやってみたいと思います。

Kotlin用不変コレクションライブラリ kollection 0.2リリース

  • リストベースの集合であるKListSet
  • リストベースのマップであるKListMap
  • 遅延評価を提供するKLazy

のサポートを追加しました。また、必要になった時点で、KListにもメソッドを随時継ぎ足していってます。詳細についてはREADMEを参照ください。

github.com

Kotlinでメソッド定義にrunを使う意義

Kotlinにはrun というメソッドがstdlibにあります。これ、定義をみると、

@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R = block()

引数で渡された0引数ラムダ式(とKotlinの用語法に従っておく)をそのまま呼び出すだけというものになっています(もう一バージョンあるのですが、そっちについては割愛)。本来の意味での用途かはわからないですが、これが役に立つケースとして、メソッド定義でreturnを書かなくても済むというものがあります。

たとえば、引数を取り、それぞれを出力した後、二つの値を足したものを返すメソッドprintAndAddは、通常は以下のように書く必要があります。

fun printAndAdd(x: Int, y: Int): Int {
  println(x)
  println(y)
  return x + y
}

さて、このreturn必須というのが、(式ベースの言語から来ると)地味に嫌なわけですが、この制限をrunを使って回避することができます。以下のように書き換えるだけです。

fun printAndAdd(x: Int, y: Int): Int = run {
  println(x)
  println(y)
  x + y
}

runにその場で生成したラムダ式を渡して実行するという形になりますが、ラムダ式の返り値の型をKotlin処理系が推論してくれるため、書く必要がなくなっています。

ところで、メソッド呼び出しのたびにラムダ式が生成されるというと実行コストが気になる人がいるかもしれませんが、心配ご無用。runにインラインで渡された無名関数はそのままインライン展開されるため、オーバーヘッドはほぼゼロです。

試しに、次のコードをコンパイルしてみます:

fun add(x: Int, y: Int): Int = run {
  x + y
}

javapした結果:

Compiled from "P.kt"
public final class PKt {
  public static final int add(int, int);
    Code:
       0: nop
       1: iload_0
       2: iload_1
       3: iadd
       4: ireturn
}

nopが入ってしまっているのが多少残念ですが、さすがにこのレベルで無駄なコードはJITコンパイラが消去してくれることを期待できますし、万が一消去してくれなかったとしても、メソッド一つにつきnop命令一つ分のオーバーヘッドはほぼ無視できます。

ただ、性能面での問題点はないものの、Kotlinの標準的な慣習に沿っていないという点はやや微妙かもしれません。まあ、その辺は統一してrunを使っていれば問題は少ないのではないかとも思いますが。