2011年05月05日

Scalaよもやまメモ

コップ本読んでる時にいろいろメモを残していたので、その中から面白みがありそうなものを貼ってみる。



finallyでreturnした場合の挙動

// finallyでreturnすると、finally句が優先される
def test1 : Int = try { return 1 } finally { return 2 }
println(test1)
//=> 2

// returnしない場合は、tryのブロック内で最後に評価された値が
def test2 : Int = try { 1 } finally { 2 }
println(test2)
//=> 1

finallyで評価が返っちゃうと、ブロック内のものを返すの面倒だから、後者が正しいのだろうけど、明示的にreturnと書いたらfinallyの中でも返っちゃうよ、ということでいいのだろうか。

うっかりすると間違えそう。



ScalaのREPLで評価した値

ScalaのREPLで値を評価すると、こんな風に表示される

scala> 10 + 2
res0: Int = 12

ここに出てくる「res0」は変数になっているので、使いまわせる。

scala> res0 * 2
res1: Int = 24

ちなみに「res0」は「val」なので、再代入しようとすると怒られる。

scala> res0 = "3"
<console>:6: error: reassignment to val



Scalaは演算子もメソッド

普通の関数呼び出しはドットで繋ぐ。

scala> Array(1, 2, 3).mkString(",")
res0: String = 1,2,3

でも、実はドットとかいらない。

scala> Array(1, 2, 3) mkString(",")
res1: String = 1,2,3

引数が1つなら括弧も使わなくていい。

scala> Array(1, 2, 3) mkString ","
res2: String = 1,2,3

これは「+」や「-」などの演算子も同じ。

普通に書くと、こう。

scala> 1.5 * 3.1
res3: Double = 4.65

でも、演算子もメソッドなので、ドットと括弧で繋ぐこともできる。とても見づらい表記になるけど。

scala> 1.5.*(3.1)
res4: Double = 4.65

Intでこれをやると、ドットが小数点と思われてDobuleになったりする。

scala> 3.+(2)
res5: Double = 5.0

ので、括弧でくくってあげたり。

scala> (3).+(2)
res6: Int = 5

ブロックにするなど明示的な書き方をする。

scala> {3}.+(2)
res7: Int = 5

いや、こんな表記普通しないけど。



Tupleは22個まで

Tupleは複数の型を気にせずにひとまとめにできてしまうクラス。違うクラスのインスタンスを括弧で囲むと出来上がり。

scala> ("string", 10, 'A')
res30: (java.lang.String, Int, Char) = (string,10,A)

これは引数の数に応じて、Tuple1, Tuple2, Tuple3というクラスが生成されている。

3つだとTuple3

scala> ("string", 10, 'A').getClass
res33: java.lang.Class[_] = class scala.Tuple3

4つだとTuple4

scala> ("string", 10, 'A', 0).getClass
res34: java.lang.Class[_] = class scala.Tuple4

Scala2.8ではTupleはTuple1〜Tuple22が定義されている。
http://www.scala-lang.org/api/current/scala/package.html

なので、22を超えるとエラーになる。

scala> (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23).getClass
<console>:6: error: value Tuple23 is not a member of package scala

どうして22なのかは調べてない。もし22を超えるような必要性が出たら(主にcase classとかでありえるのだろうけど)、Scalaがそれ以上のTupleが持てないことを疑問に思う前に、そんなデータ構造になってしまっていることを見直す必要があると思われる。



関数のイコールを省略すると、Unit型になる

scala> def f1 = "f1"

こう書くと、この関数は戻り値がStringであると推論されて、"f1"というString型を返す関数になる。

scala> f1
res1: java.lang.String = f1

イコールを省略した関数は、Unit型(voidっぽいもの)になる。例えば以下のように書いた場合は、Unit型。

def f2 {"f2"}
f2: Unit

これを呼びだした場合は、"f2"が返ってきたりはしない。宣言のところにイコールを足すと、文字列が返るようになる。

def f3 = {"f3"}
f3: java.lang.String

この辺りは慣れると納得の挙動だけど、慣れないうちは分りづらいかもしれない。



Scalaがデフォルトでimportしているもの

1. java.lang配下

なので、java.lang.Integerとか、java.lang.Stringとかはimportしないでも使える。

2. scala配下

Arrayとか、Listとか、Tupleとか。

詳しくはこの辺
http://www.scala-lang.org/api/current/scala/package.html

3. scala.Predefというobjectの中身

printlnとかがいる。Predefの中にいる子は組み込み関数的に使えることになる。

Predefの詳しい中身はこの辺。
http://www.scala-lang.org/api/current/scala/Predef$.html

ちなみにPredefの中の関数名とぶつかった命名をすることはできたりする。

scala> val println = "test"
scala> print(println)
test

この辺はobjectをhoge.Object1._みたいにimportした時の挙動と同じ。



Applicationを継承すれば、main関数を書かなくてもいい

通常はJavaっぽいmain関数を書くことになる。

object Test {
def main(args : Array[String]) = println("hello world")
}

Applicationをextendsすれば、main関数を書かなくても実行できたりする。

object Test extends Application {
println("hello world")
}

この場合、2.8だとコマンドライン引数を使えないけど、2.9だとどうにかなるという話を聞いたような。



変数名に使える文字

日本語とかは普通に使える。

scala> val テスト = "test"
テスト: java.lang.String = test

あと、リテラルを変数名に使うこともできる。

scala> val \u30c6\u30b9\u30c8 = "test"
テスト: java.lang.String = test

記号もけっこう使えるけど、組み合わせによっては文法的に問題になったりするので使いたくはない。

たとえば「!+?」なんていう無茶な変数名も使える。

scala> val !+? = "test"
!+?: java.lang.String = test

でも、英字と組み合わせるとたいていエラーになる。

scala> val what? = "test"
<console>:1: error: illegal start of simple pattern

「+」という変数名も付けられる。

scala> val + = 10
+: Int = 10

ということは、こんな楽しいソースが書けるのか、と思ったけどエラーになって無理。

scala> val - = + + +
<console>:6: error: ambiguous reference to overloaded definition,

変態ソース愛好家にとっては深く探求したくなる領域かもしれないけど、そうじゃなければ避けて通りたいところ。



Scala本体のソースのヘッダ

Scalaの標準ライブラリなどのソースには、こんな感じのヘッダが入ってる。

/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */

なんか頑張ってる的な感じがします。日本の環境だとバックスラッシュ問題が見え隠れする。



Scalaの再帰の最適化

たとえば1万回ループするコード。

def loop(i : Int) : Int = i match {
case 100000 ⇒ i
case _ ⇒ loop(i + 1)
}
loop(0)

これは1万回loopという関数が呼び出されることになるので、オーバーヘッドもさぞかしすごかろうと思われるかもしれないけど、再帰についてはコンパイラが最適化してくれる。

上記コードをclassファイルにコンパイル後、JDでデコンパイルするとこんな感じのwhile文になってる。

private Test$() {
this.i = 0;
while (i() < 100000)
i_$eq(i() + 1);
}
}

但し、複数の関数を利用した再帰は最適化できないらしい。大掛かりな処理をする時はどう最適化されるかも少し考える必要があるかも。



そういえばScalaは引数指定も省略できる

関数を宣言する時に括弧内に書かなくても、引数を利用する場所で下記のように書けば動たいりする。

scala> def foo = (_ : Int) + (_ : Int)
scala> foo(10, 20)
res0: Int = 30

これはこう書くのと同じ意味になる。

scala> def foo(i: Int, j: Int) = i + j

この辺はforeachの中で使ってるアンスコと一緒か。



引数なし関数呼び出しの括弧は省略すべきか

Scalaでは引数がない関数を呼び出す際は、括弧は省略できる。

def hello() = println("hello")
hello

コップ本では副作用がない場合は省略、ある場合は括弧を付けるような習慣だとされている。

配列やリストのlengthとかsize等、フィールドにアクセスするのと変わらない気持ちで使う関数は括弧を省略する。

逆に上のhelloのような標準出力や、ミュータブルな変数の操作、DBやファイルへの出力といった副作用がある場合は括弧を明記する。

こうやって色分けしておくと、確かに感覚的にも分かりやすい気がする。

ちなみにScalaの継承では、引数なしのdefをvalでoverrideできたりする(逆は不可)。こんな感じで。

class Parent {
def foo : String = "parent"
}
class Child extends Parent {
override val foo = "child"
}



AnyRefの実態はjava.lang.Object

Scalaのプリミティブ系以外のすべてのクラスの親であるAnyRefについて。

こう打つと
scala> val anyRef = new AnyRef

こう返ってくる
anyRef: java.lang.Object = java.lang.Object@634e3372

なので、isInstanceOfでjava.lang.Objectと比較してもtrueになる。
scala> anyRef.isInstanceOf[java.lang.Object]
res0: Boolean = true

もちろん、AnyRef本体と比較してもtrueになる。
scala> anyRef.isInstanceOf[AnyRef]
res1: Boolean = true



1つのファイルに複数パッケージを定義

Scalaのパッケージは、Javaの記法そのままの書き方もできるし

package com.sample

下記のように括弧でくくって、1つのファイルに複数パッケージを定義することができるし

package com.sample {
class Foo { }
}

下記のように入れ子にすることもできる。

package com {
package sample {
class Foo { }
}
}