kmizuの日記

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

Kotlinのコレクションに対してstreamメソッドが呼び出せない

Java 8でjava.util.Collectionに追加されたメソッドとして、Streamを返すstream()メソッドがあります。これ、java.util.Collectionに追加されたメソッドなのだから、当然Kotlinでも呼び出せて良いはずですが、実はできません。

>>> val x = listOf(1, 2, 3, 4, 5)
>>> x.stream()
error: unresolved reference: stream
x.stream()
  ^

Scalaでは何の問題もなくできます(なお、SAM Type変換を行っているので、Scala 2.12以降でのコードです)。

scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._

scala> val x = List(1, 2, 3, 4, 5).asJava
x: java.util.List[Int] = [1, 2, 3, 4, 5]

scala> x.stream()
res0: java.util.stream.Stream[Int] = java.util.stream.ReferencePipeline$Head@3739f3c9

scala> x.stream().filter{e => e % 2 == 0}
res1: java.util.stream.Stream[Int] = java.util.stream.ReferencePipeline$2@59371066

さて、何故、このような差が生まれてしまったのでしょうか。KotlinではJavaのコレクション・フレームワークを、実装上は再利用しつつ、型としては違う振る舞いを与えているからです。そのため、無理やりjava.util.Collectionにキャストしてやると、stream()メソッドが呼び出せるというめんどくさい挙動になっています。

>>> val x = listOf(1, 2, 3, 4, 5)
>>> (x as java.util.Collection<Int>).stream()
java.util.stream.ReferencePipeline$Head@15d0c81b
>>> (x as java.util.Collection<Int>).stream().filter{it % 2 == 0}
java.util.stream.ReferencePipeline$2@587c290d

よーするにKotlinのコレクションはJavaのコレクションだといいながら、型としてはJavaのコレクションだと思って扱うと上手くいかないという悲しい例です。 JavaとinteroperableじゃないKotlinの悲しい一面です。

ところで、

kotlinlang.org

をみると、

Kotlin supports Java 8 streams, which provide similar functionality

と書いてありますが、こういうのをみると、どこがやねん(何故か関西弁っぽい)とツッコミたくなります。

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

しばらく触っていなかったのですが、ふと思い立って、データ構造を追加してみました。

具体的には、

の二つを追加しました。実装はほとんど、Scala版のRedBlackTreeのポーティングですが、is乱発しなければいけなくて、結構めんどかったです。

GitHub - kmizu/kollection: Immutable Collection Libraries and Additional Utilities for Kotlin

Kotlinのnullable typeはzero overheadではありません

タイトルの通りなんですが、JetBrainsさんが公式で

Zero-overhead null-safety

とか書いているおかげで信じている人もいるかもしれません。ですが、それは正しくありません。

試しに、次のコードをコンパイルしてみましょう。

fun double(x: Int?): Int = x?.let{ it * it } ?: 0 

クラスファイル BoxingKt.class が生成されるので、javapしてみましょう。

Compiled from "Boxing.kt"
public final class BoxingKt {
  public static final int foo(java.lang.Integer);
    Code:
       0: aload_0
       1: dup
       2: ifnull        21
       5: astore_1
       6: nop
       7: aload_1
       8: checkcast     #9                  // class java/lang/Number
      11: invokevirtual #13                 // Method java/lang/Number.intValue:()I
      14: istore_2
      15: iload_2
      16: iload_2
      17: imul
      18: goto          23
      21: pop
      22: iconst_0
      23: ireturn
}

まず、引数の型がjava.lang.Integerになっています。これは、引数を渡す時にBoxingのコストが発生することを意味します。また、その値を使って計算する場合、Unboxingのコストが発生します。

この問題は、プリミティブな型をnullableにしたときに発生します。ジェネリクスでプリミティブ型相当を引数にしたときにBoxingが発生するのと理屈は同じです。

必ずしもこの点が問題にはならないとは思いますが、一般にKotlinのnullable typeがzero overheadというのはだと言って良いでしょう。

Klassic開発日誌(2016/10/08):型チェックはじめました

冷やし中華はじめました、みたいなこと書いてみたかっただけです。はい。

これまでKlassicでは、メインのインタプリタは(あとで静的型をつけることを意識しつつ)動的型言語でしたが、少しずつ静的型を加えていっています。現在のところ、まだ多相型を扱えないという制限はあるものの、

val x = 1 // xはInt
x = 2 // 再代入不可

mutable y = 1 // xはInt
y = 2 // 再代入可能

val list = new ArrayList
list.add(1)
list.add(2)
list.add(2)
println(list) // リストリテラル。[1, 2, 3] 

println([1 2 3]) // [1 2 3]
println([
  1
  2
  3
]) // [1 2 3]

val map = #["key": "value"]// マップリテラル。
println(map)

などのコードが動きます。多相型が必要なところは現在Dynamicという型を導入することでごまかしています。階乗を計算するコードは、

def fact(n) = if(n < 2) 1 else (n * fact(n - 1))
println(fact(4))

のように書けますが、今のところ引数の型宣言を省略するとDynamicになるというなんとも微妙な仕様になっています。

また、substring関数の型は(Int, Int) => Dynamicのようになっています。この辺はJavaとのinteroperabilityを考えつつ慎重に設計したいところです。ただ、Klassicの開発目的の一つとして、サブタイピングの適切な制限というのがあって、その辺は、Javaとのinteroperabilityを考えると使い勝手が下がるので難しいところです(もちろん多数の先行研究があるのは承知の上ですが、とりあえずしばらくはテキトーに実装してみて、つまづいたら先行研究を参照しようかと思います)。

JSONのようでそうでないデータフォーマットNSON 0.0.1リリース

きっかけは、昨夜の思いつきでした。

JSONでも以前から、末尾のカンマを許すようにしてほしいとかそういう要望があったと記憶していましたが、そもそもカンマを要らなくすれば解決では?というのが動機です。

NSONは次のような特徴を持っています。

  1. オブジェクトの属性を区切るカンマが必要ない
  2. 配列の要素を区切るカンマが必要ない
  3. 属性のキーとして、文字列リテラルの他に通常の識別子が使える
  4. JSONの完全上位互換である

まず最初の点についてですが、たとえば、

{ "x": 1 "y": 2}

のような記述ができるということです。また、改行で要素を区切って

{ "x" : 1
  "y" : 2 }

のように書くこともできます。

その次の点ですが、これは最初の点と同じようなものです。

{ "like": ["Scala" "Nemerle" "Scheme"] }

このように、配列の要素をカンマで区切る必要がありません。

3番目の点ですが、これは、JavaScriptでは許されていたものの、JSONでは許されなくなった点の一つであり、たとえば、

{ x: 1 y: 2}

のような記述が許されます。

最後の点ですが、これはそのままです。JSONで正しいデータはNSONでも正しいデータです。たとえば、

{ "x": 1, "y": 1}

は正しいJSONのデータであり、かつ、正しいNSONのデータです。

詳しくは、以下のURLを参照してください。

GitHub - kmizu/nson: NSON: an object notation that is not a JSON but alike JSON.

NSONは構文上の実験であり、今後メンテナンスしていくかは正直微妙です。これを拡張していくことで何か書きやすいデータフォーマットが生まれる可能性もあるので、思いついたら何か手を加えていくかもしれません。

ちなみに、思いついてからNSONのパーザを書いて、ある程度動作するようになるまで3時間くらいでした(自作のKlassic言語のパーザがある程度流用できたので、ちょっとズルいかもしれません)。こと構文解析においてはさすがだな自分と自画自賛しておきます。

Onion開発日誌/2016/11/05 単一式を本体として持つメソッドや関数定義を可能に

最近、Klassicと同時にOnionもちょくちょく更新するようになったので、変更点など書いていこうと思う。

今日は、メソッドや関数定義に、文ではなく式を取れるようにする改良を行った。今どきの言語だと当たり前だが、Onionを最初に開発した2004-2005年の時点ではそれほど当たり前ではなかったのだ…と思う。

というわけで、

class Adder {
public:
  def add(x: Int, y: Int): Int = x + y;
}

のような定義が可能になった。セミコロンが必須なところがださいので、この辺もじきに不要になるようにしたい(多少面倒だが)。

github.com

Scala初学者はvarを使っても良い

TwitterScala関係のつぶやきをみていると、どうも、特に初学者の方に、

varを使ってはいけないので、Scalaは難しい…

という意見が散見されるようです。

しかし、Scalaを学び始めのときにいきなりvarを断つ必要はなく安心してvarを使ってもいいと思います (そもそもvarが全く不要なら言語仕様にあるわけないですし)。慣れれば、varを明らかに使わなくて 良い箇所はわかってくるので、そこからval/varを使うのを意識しても遅くないと思います。

もちろん、いつもでも全部var使ってるとどうかという議論はあるのですが、それはScalaに慣れてから 考えればよい話です。Scalaを学ぶのにvarを使わないという縛りがきついのなら、無理しなくていいよ というのが言いたいことです。