-- / --
--
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

12 / 19
Thu

Ziphil じゃないときがあるはずないのに、 毎回名乗っている Ziphil です。

前のエントリーで、 Kotlin のアクセス修飾子について触れました。 そこで internal は 「モジュール内で可視」 という意味だと言ったんですが、 「モジュール」 って何やねんという話なので、 ちょっと調べてみました。

さて、 ここに説明がありました。

モジュールは、 Kotlin におけるコンパイル時の一単位です。 本質的には、 モジュールはビルドスクリプトとして表現されます。 モジュールそのもの, そのモジュールの従属関係, そのモジュールを構築するためのコンパイラのオプションなど、 これらを構成するソースファイルに関する情報を伝えるスクリプトは、 プログラム内で 「モジュール記述子」 というものを構築します。 コンパイルの一単位であるということは、 解析や最適化の全てがモジュールに従って行われるということであり、 バイナリが両立可能かどうかに関する問題が、 モジュール内でしか起こらないということでもあります。

(´・ω・`)? つまりどういうことですか?

いまいちよく分からないのが現状です・・・。 私の推測でしかありませんが、 クラスを統括するのがパッケージで、 パッケージを統括するのがモジュール、 みたいな感じでしょうか。

というか、 プログラミング関連の英語の専門用語の知識がなさ過ぎて、 英語読めても全然意味分からないです。


スポンサーサイト
comment ×0
12 / 19
Thu

Kotlin Advenent Calender 2013 に勢いで参加してしまった Ziphil です。

19 日目のエントリーになります。 せっかく Kotlin について何か書くんだし、 Kotlin じゃないとできないようなことについて書きたかったんですが・・・、 いまいち思いつきませんでした! ・・・ということで、 12 月 6 日にリリースされた Kotlin M6.2 での新機能、 末尾再帰最適化についてもう少し詳しくまとめてみようと思います。 少し詳しめに書いたので、 もともとこれについて知ってる人は、 特に得るものはないかもしれません・・・。

さて、 「末尾再帰」 とは、 とあるサブルーチン内の最後の命令として、 他のサブルーチンが呼ばれることです。 つまりは、 こんな感じのコードですね!

fun fibonacci(n: Int, a: Int = 0, b: Int = 1): Int {
  if (n < 1) {
    return a
  }
  return fibonacci(n - 1, b, a + b)
}

フィボナッチ数を求めるコードでーす。 if 文で場合分けされているものの、 関数 fibonacci の 1 回分で最後に実行されるのは、 関数 fibonacci そのものになっています。 関数型言語なんか (Scala とか) では、 こういう書き方の方が好まれますね!

で、 この末尾再帰にはちょっとした問題があるんですが・・・。 例として、 fibonacci(4) を考えてみましょうか。 Kotlin は、 4 番目のフィボナッチ数を、 こんな感じで順に計算します。 ・・・あ、 計算するのは JVM ですけど、 Kotlin が計算してるって考えた方がかわいいですよね!

  • fibonacci(4)
  • fibonacci(3, 1, 1)
  • fibonacci(2, 1, 2)
  • fibonacci(1, 2, 3)
  • fibonacci(0, 3, 5)
  • 3

さて、 こんな感じでようやく 3 が得られます。 ちょっとした問題というのは、 この計算の中に潜んでいます。

fibonacci(4) が呼ばれると、 Kotlin は fibonacci(3, 1, 1) を返そうとしますが、 この値は分かっていません。 そこで、 Kotlin は 「fibonacci(3, 1, 1) の値を計算し終わったら、 その値を関数の返り値として返すぞー」 ということを覚えておいて、 fibonacci(3, 1, 1) の値を求めにいきます。 すると、 今度はその結果は fibonacci(2, 1, 2) で、 これも値が分かっていないので、 Kotlin はまた 「fibonacci(2, 1, 2) の値が分かったら、 それを返すぞー」 と覚えておきます。

とまあ、 こんな風に、 再帰的に fibonacci が呼ばれるたびに、 Kotlin は 「あれを求めた結果を返そう」 と頭に覚えるわけですが、 fibonacci(100) を実行しようと言われちゃったら、 もう頭がパンパンになって破裂しちゃうわけですよ。 ちなみに、 こういうのを 「スタックオーバーフロー」 と言ったり言わなかったり・・・。 とにかく、 困りますねー。

そこで、 M6.2 から新しく実装された 「末尾再帰最適化」 が便利なんですね。 これによって、 Kotlin が頭の中に覚えておくべき内容が減って、 fibonacci(100) でも楽々計算できるようになるわけです。 Kotlin がより賢くなるのです。 やったね!

末尾再帰最適化の使い方はこうです。

tailRecursive fun fibonacci(n: Int, a: Int = 0, b: Int = 1): Int {
  if (n < 1) {
    return a
  }
  return fibonacci(n - 1, b, a + b)
}

はい、 アノテーション tailRecursive をつけるだけ! 簡単でしょ?

これで本当に頭がパンパンにならずに計算できるようになったのかなぁ? ・・・と疑いの目を向けている方もいるかもしれませんね。 じゃ、 コンパイルされたバイトコードを逆コンパイルしてみましょう。 どうなってるでしょうか。

まずは、 tailRecursive アノテーションをつけず、 最適化をしなかった場合のバイトコードです。 ちなみに、 デフォルト引数の処理は別の関数を介しているみたいで、 その部分は省略したので、 下のコードはデフォルト引数の情報がなくなってます。 あ、 それと、 読みやすいように多少コードを整形してあります。

public static final int fibonacci(int n, int a, int b) {
  if (n < 1) {
    return a;
  } else {
    return fibonacci(n - 1, b, a + b);
  }
}

・・・見事にそのまんまですね! まあ、 そりゃそうなんですけど。

じゃ、 次に tailRecursive アノテーションをつけて、 最適化をした場合のバイトコードです。 こんな感じー。

public static final int fibonacci(int n, int a, int b) {
  do {
    if (n < 1) {
      return a;
    }
    b = a + b;
    a = b;
    n = n - 1;
  } while (true);
}

お? do ~ while ループが入ってますねー。 これによって、 「これを計算したらこれを返す」 とかいう無駄な記憶をせずに、 単にループをこなすだけでよくなるわけです。

ちなみに、 末尾再帰最適化をすると、 使用するスタックの量を節約できるだけでなく、 速度も向上するようです。 最適化をする場合としない場合とで、 50 番目のフィボナッチ数を計算してみると、 私の環境では最適化をした方が 40 倍も速いことが分かりました。

ところで、 疑問に思った方もいるんじゃないでしょうか? 末尾再帰してる関数があったら、 Kotlin が自動的に最適化してくれれば良いのにー、 とか。 いちいち tailRecursive とか面倒だよー、 とか。 公式ブログにちゃんと説明が書いてありました。

デバッグのためです。 末尾再帰が最適化された関数では、 実際に再帰呼び出しがされるわけではないので、 デバッガによって、 再帰されていることを示すスタックフレームが残されず、 再帰の前段階におけるローカル変数について詳しく調べることができなくなります。 したがって私たちは、 このことに対する最も普通な解決策は、 それをコード内で明示的に行うことだと思っています。

それともう 1 つ。

安全性のためです。 皆さんがよく知っているように、 コードというものは、 行うだろうと予想される処理を行わないことがありますよね (≧∇≦)。 同じことが末尾再帰に対しても成り立ち、 末尾再帰のような関数呼び出しが、 実はそうでなかったということもよくあります。 これによって、 最適化がなされずパフォーマンスの向上が妥協されてしまいます。 しかし、 tailRecursive アノテーションによって、 コンパイラは、 どの関数呼び出しが最適化されるべきか分かり、 間違いがあった場合は警告を発することができるのです。

・・・ということで、 アノテーションは省略できないようになったわけですね。

さて、 こんな感じで、 Kotlin の末尾再帰最適化について書いてみましたー。 Kotlin を知ったのが最近であるという上に、 この最適化処理を知ったのも最近なので、 至らない部分など多いと思いますが、 温かい目で見守ってください・・・。


comment ×0
12 / 17
Tue

最近寒くなったと感じる Ziphil です。

Kotlin は Java に比べてコードが短くなるという話なので、 Kotlin 初心者ながらも、 コードゴルフに挑戦してみました。 テーマは 1 から 100 までの FizzBuzz です。 なお、 FizzBuzz の結果は標準出力に表示するものとして、 fun main(args: Array<String>) とかを除いた、 処理部分だけを短くします。 それと、 標準ライブラリ以外はインポートしないものとします。

さて、 まずは何も気にせず普通にコードを書いてみましょうー!

for (i in 1..100) {
  if (i % 15 == 0) {
    println("FizzBuzz")
  } else if (i % 3 == 0) {
    println("Fizz")
  } else if (i % 5 == 0) {
    println("Buzz")
  } else {
    println(i)
  }
}

200 Byte です。 これをこれから短くしていきます! まず、 for 文は in やその前後のスペースとかで文字数を稼いでしまっているので、 代わりに forEach を使ってみます。 ついでに、 不要なスペースやカッコを除去します。

(1..100).forEach{if(it%15==0)println("FizzBuzz")else if(it%3==0)println("Fizz")else if(it%5==0)println("Buzz")else println(it)}

127 Byte です。 さて、 コードゴルフでは、 同じものが繰り返されているは良くないと言われていますね。 コードを見てみると、 else if とか else が何個もあって文字数を稼いでいるので、 これを何とかして消したいものです。 そこで、 when 文を使ってみます。 Kotlin の when 文は、 単に if ~ else if の連続を見やすく書き直すことにも使われます。

(1..100).forEach{when{it%15==0->println("FizzBuzz");it%3==0->println("Fizz");it%5==0->println("Buzz");else->println(it)}}

121 Byte です。 さて、 println もたくさんありますから、 これも 1 つにまとめちゃいましょう!

(1..100).forEach{println(when{it%15==0->"FizzBuzz";it%3==0->"Fizz";it%5==0->"Buzz";else->it})}

93 Byte です。 100 Byte を切りましたが、 ここからが難しいんです・・・。 まず、 剰余算の返り値は必ず 0 以上の整数なので、 == 0< 1 に置き換えて、 悪あがきします。

(1..100).forEach{println(when{it%15<1->"FizzBuzz";it%3<1->"Fizz";it%5<1->"Buzz";else->it})}

91 Byte になりました。 うー・・・。 限界ですかねぇ・・・。 FizzBuzz が 2 回出てくるので、 これをそれぞれ 1 回ずつにしたいところですが・・・。 そこで、 Fizz と Buzz の表示判定をそれぞれ別々に行ってみましょう。

(1..100).forEach{println(when{it%3<1->"Fizz";else->""}+when{it%5<1->"Buzz";else->""})}

これだと 3 の倍数でも 5 の倍数でもない場合に数字が表示できませんね・・・。 だったら、 最後に数字をつけ足せばいいじゃない。

(1..100).forEach{println(when{it%3<1->"Fizz";else->""}+when{it%5<1->"Buzz";else->""}+"${it}")}

・・・いやいや、 これだと Fizz とかを表示する場合も数字が表示されちゃいます。 だったら、 Fizz とか Buzz が表示される場合は数字を消せば良いじゃない。

(1..100).forEach{println((when{it%3<1->"Fizz";else->""}+when{it%5<1->"Buzz";else->""}+"${it}").replaceAll("z\\d+","z"))}

120 Byte です。 いや、 増えてるじゃないですか!! ・・・んー、 ということで、 何か使える関数がないか、 Kotlin の API を眺めてみると・・・、 drop ってのがありました! これを使うと、 指定した文字数分だけ最初から取り除いた文字列が得られます。 これで when 文が消せるんじゃないか・・・?

とりあえず、 Fizz の表示判定を考えてみます。 it が 3 の倍数のとき 0 になり、 それ以外なら 4 になるような計算式があれば、 それを drop の引数にすれば、 うまくいきそうです。 できるのか・・・?

考えた結果、 導き出された式は it % 3 * 4 です。 3 の倍数でないとき 4 じゃなきゃいけないわけじゃないんです。 要は 「Fizz」 って文字列を全部消したいだけですから、 4 以上なら何でも良いわけです。 ということで、 書き直します。

(1..100).forEach{println(("Fizz".drop(it%3*4)+"Buzz".drop(it%5*4)+"${it}").replaceAll("z\\d+","z"))}

100 Byte です。 うわ、 短くなってないですねー・・・。 "Fizz".drop(it%3*4) という発想は良かったかもしれませんが、 やっぱり replaceAll 関数が文字数取ってますね・・・。

結局、 私には 91 Byte が限界でした。 最後の drop の発想は良かったと思うんですけどねー。 substring も使えそうなんですが、 そもそも関数名が長いから乱用できませんし。

コードゴルフ、 考えるのは楽しいですけど難しいですね。 80 Byte くらいにはなりそうですが、 良い方法が思いつきません。 皆さんも考えてみてはどうでしょうか?


12/17・・・が、 Twitter でコードゴルフをやってみたということを呟いたら、 @ngsw_taro さんからさらに短くなったコードをいただきました。

(1..100)map{a->println(when{a%15<1->"FizzBuzz";a%5<1->"Buzz";a%3<1->"Fizz";else->a})}

85 Byte です。 forEach にする必要がなかったというのは盲点でしたね。


12/27・・・これが最短かなーとか思っていたら、 @ayato_p さんから指摘が。

1..100map{a->println(when{a%15<1->"FizzBuzz";a%5<1->"Buzz";a%3<1->"Fizz";else->a})}

83 Byte になりました。 カッコいらないんですねー・・・。


comment ×0
12 / 08
Sun

「blockquote」 を打とうとするとたいてい 「blocuqote」 になってしまう Ziphil です。

この記事でゲッタとセッタに関するエラーについて書きましたが、 気になったので Kotlin のフォーラムで質問してみました。 するとこんな答えが。

プロパティ foo に対する getFoo, setFoo メソッドは、 あなたがおっしゃる通り、Java との連携のために生成されています。

・・・ということで、 思った通りでした。

非常に古い要望 (KT-1) に、 JVM と同じ名前をもつ関数の定義を禁じてほしいというものがありました。 また、 この場合に対して、 新しい要望 (KT-4287) をしておきました。

うーん・・・。 やっぱり getFoo, setFoo みたいなメソッドの定義は推奨されないようですし、 おそらく禁止されるでしょう。 まあ、 そりゃそうですけどね、 冗長ですし。

Java との一貫性は諦めて、 Kotlin の機能を利用しましょうかねぇ・・・。


comment ×0
12 / 08
Sun

視力が 0.03 の Ziphil です。

IntelliJ IDEA 13 と Kotlin M6.2 がリリースされてましたね。 ということで、 Kotlin M6.2 の新機能を調べてみました。

関数型プログラミング好きの人に良いお知らせです。 Kotlin は末尾再帰最適化 (Tail Call Optimization, TCO) に対応しました。 これによって、 スタック領域を使いすぎる心配をすることなく、 関数型的なコードを書けるようになりました。

・・・なるほど。 で、 「末尾再帰最適化」 というのは何なのか調べてみると、 再帰的に関数を呼び出すような関数を効率良く実行できるようにすることらしいです。

知られている通り、 Kotlin は型に非常に厳密で、 数値型でさえも暗黙に型変換されるのが許されていません。 これは、 全ての型をクラスとして同様に定義し、 ときどき Java によって作られる奇妙なコードを避けるために、 不可欠なものです。 幸いにも、 Kotlin には定数表現に関する機能が組み込まれているため、 これによって多少不便になるような部分を、 ほとんど除くことができます。 今回のアップデートで、 この機能が完成され、 val x: Long = -1val x: Byte = -1 のように書くことができるようになりました。 より詳しく言えば、 コンパイラが定数表現を計算し、 その定数が予期されている (Long や Byte といった) 型に対応できるかどうか調べ、 定数を変換する、 ということが内部で起こっています。

確かに、 Int 型以外の数値の変数を初期化するとき、 val x: Long = 1.toLong() と書かなければいけなくて、 少し面倒でしたね。 今回のアップデートで、 型変換を自分でやる必要がなくなったわけです。

また、 よく知られている接尾辞 「L」 を数値につけることができるようになりました。 なお、 Long 型に対する接尾辞 「l」 は、 この文字が多くのフォントで 「1」 と良く似ているということで有名なため、 使うことが許されていません。 同様に、 Float 型に対し接尾辞 「F」 を使えるようになりました。 「f」 はどの数字にも似ていないし、 けっこう良い感じに見えるので、 こちらは使うことは禁止されていません。

Java にもあった、 型を示す接尾辞が実装されたようです。 「1」 と似てるからといって 「l」 を禁止する必要はなかった気もしますが、 見やすいコードを書かせるためでしょうかね。

M6.2 での主なアップデートはこの 2 つでしょうか。 これ以外にも Java との連携などに関する新機能がいくつかありますが、 ここで詳しくは説明せず、 箇条書きにとどめておきます。 詳細は、 公式ブログを見てください。

  • Java で @ReadOnly アノテーションをつけることで、 Kotlin ではイミュータブルな配列にすることができるようになった。
  • Kotlin のコードをコンパイルすると、 バイトコードに @Nullable と @NotNull が自動的につけられるようになった。
  • Android SDK 用のアノテーションが追加された。
  • JavaScript への変換が改良された。

これに合わせて、 IDEA 13 の方もアップデートされていて、 コード保管などが改良されているみたいです。


comment ×0
12 / 05
Thu

紙で中指を結構深く切ってしまった Ziphil です。

前のエントリーの続きです。 Kotlin のプロパティの概念は素晴らしいと思うんですが、 これを使うと Java との統一感が失われてしまいます。 ということで、 Java の get, set で始まるメソッドを、 Kotlin のプロパティのゲッタとセッタの呼び出しのように書けるようになれば、 統一感もありますし、 コードも綺麗になります。 ついでに、 引数のないメソッドはカッコを省略できるようにもしてほしいですね。 つまり、 こういうことです。

// 従来の書き方
buffer.setFont(Font("sans-serif", 0, 12))
buffer.setColor(Color(0xFF, 0xFF, 0xFF))
val y: Int = buffer.getFontMetrics()!!.getAscent() * 2 / 3 + 5
// 提案する書き方
buffer.font = Font("sans-serif", 0, 12)
buffer.color = Color(0xFF, 0xFF, 0xFF)
val y: Int = buffer.fontMetrics!!.ascent * 2 / 3 + 5

・・・と思っていましたが、 同じことがここですでに提言されてました。 Kotlin 製作者の応答はこんな感じです。

あなたの提言にはかなりの微妙な点があります。 Kotlin は、 強い静的型付け言語であるという点 Groovy との大きく異なっているので、 厳密にものごとを考えなくてはなりません。 例えば、 もし何らかのクラスから name と getName の両方のメソッドを継承したとしたら、 そのプログラムはどう動くべきでしょうか?

・・・はい、 ごもっともです。 しかし、 name メソッドが呼ばれた場合、 name という名前のメソッドが可視ならそれを実行し、 不可視なら getName メソッドを実行しようとするようにすれば、 問題はない気がするんですが・・・。


comment ×0
12 / 05
Thu

Ziphil です。

また Kotlin の謎の挙動に遭遇したので、 メモしておきます。

どうやら、 1 つのクラスに、 例えばプロパティ foo とメソッド getFoo を両方定義すると、 コンパイルは通りますが、 実行時に ClassFormatError が投げられるようです。 原因は不明ですが、 おそらく Java のバイトコードを生成する際に、 内部で getFoo を定義しているのだと思います。

まあ、 Kotlin 使っているなら、 ゲッタやセッタはいちいち書かないでしょうし、 この問題に直面することはほとんどないでしょうけどね。 私の場合、 Java との一貫性を保つためにゲッタをいちいち書いていたので、 この問題にぶつかりましたが。 じゃ、 どうしていまさらこれに気づいたかというと、 これまではちょっとした諸事情で、 プロパティの変数名の最初にアンダバーをつけていたためです。 さすがにこれでは Kotlin の慣習を壊しすぎだと思い、 全コードのアンダーバーを削除して実行したら、 エラーになったわけです。 原因が分かるまで 30 分くらい悩みました。

ちょっと困ってます。 ゲッタやセッタを手動で書かずに、 Kotlin の機能を活用すれば良いんですけど、 Java 由来のクラスの get, set で始まるメソッドはそのままなので、 統一感がなくなっちゃうんですよね。


comment ×0
back-to-top
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。