Scala School意訳(Basics)
http://twitter.github.com/scala_school/basics.html
以下は私の方でtypoや表示崩れを直したものです。
https://github.com/seratch/scala_school/blob/master/web/_posts/2011-05-01-lesson.textile
誤訳などありましたら、お手数ですが、ご指摘いただければ幸いです。
Think Scala
Scalaは単により良いJavaというだけではありません。 フレッシュな頭で学ぶ必要があります。 そうすればこの授業からより多くを得られるでしょう。
Start the Interpreter
インタプリタの起動方法として、sbt consoleが紹介されています。
sbtのセットアップなどは省略されていますが、初学者の方のために簡単に手順を書いておきます。
mkdir ~/bin cd ~/bin wget http://typesafe.artifactoryonline.com/typesafe/ivy-releases/org.scala-tools.sbt/sbt-launch/0.11.1/sbt-launch.jar vim sbt # java -Xmx512M -jar `dirname $0`/sbt-launch.jar "$@" chmod u+x sbt ./sbt console
Windowsの場合は以下のようなsbt.batを用意します。
@echo off set SCRIPT_DIR=%~dp0 java -Xmx512M -jar "%SCRIPT_DIR%sbt-launch.jar" %*
https://github.com/harrah/xsbt/wiki/Getting-Started-Setup
sbt以外での使い方については、こちらもどうぞ。
Expressions
scala> 1 + 1 res0: Int = 2 res0はあなたが実行した式の結果に対してインタプリタによって与えられた名前です。 この値はInt型で整数値2を保持しています。 Scalaでは(ほとんど)全てが式です。
(訳者追記)
「+」は演算子ではなくメソッドです。「1+1」は「(1).+(1)」のようなメソッド呼び出しのシンタックスシュガーのイメージです。
scala> (1).+(1) res4: Int = 2
のようになります。
Values
式の結果に名前をつけることができます。 scala> val two = 1 + 1 two: Int = 2 valで束縛したものを変更することはできません。 (訳者追記) scala> two = 3 <console>:8: error: reassignment to val two = 3 ^
(訳者追記)
変数の型宣言はoptionalなので「:」に続けて明示することもできます。
scala> val two: Int = 1 + 1 two: Int = 2 scala> val two: Double = 1 + 1 two: Double = 2.0
valで束縛したものを変更できないのは、Javaでいうfinal宣言と同じイメージです。
あと、以下のようにまとめて変数に代入することもできます。
scala> val (x,y,z) = (1,2,3) x: Int = 1 y: Int = 2 z: Int = 3
以下のように書くと全てに同じタプルが代入されるので要注意です。
scala> val x,y,z = (1,2,3) x: (Int, Int, Int) = (1,2,3) y: (Int, Int, Int) = (1,2,3) z: (Int, Int, Int) = (1,2,3)
http://docs.scala-lang.org/cheatsheets/
Variables
変更したい場合は、代わりにvarを使います。 scala> var name = "steve" name: java.lang.String = steve scala> name = "marius" name: java.lang.String = marius
Functions
defを使って関数を生成することができます。 Scalaでは、関数の引数に型を指定する必要があります。インタプリタは指定した型のシグニチャを表示します。 scala> def addOne(m: Int): Int = m + 1 addOne: (m: Int)Int scala> val three = addOne(2) three: Int = 3 引数がない場合は、関数の丸括弧を外すことができます。 scala> def three() = 1 + 2 three: ()Int scala> three() res2: Int = 3 scala> three res3: Int = 3
(訳者追記)
関数の定義時にそもそも丸括弧を省略した場合は丸括弧なしでしか呼び出せません。
scala> def three = 1 + 2 three: Int scala> three() <console>:9: error: Int does not take parameters three() ^ scala> three res2: Int = 3
Anonymous Functions
匿名(無名)関数をつくることができます。 scala> (x: Int) => x + 1 res2: (Int) => Int = <function1> この関数はxというInt型に1を加えます。 scala> res2(1) res3: Int = 2 匿名関数をやり取りしたり、変数に保存することができます。 scala> val addOne = (x: Int) => x + 1 addOne: (Int) => Int = <function1> scala> addOne(1) res4: Int = 2 関数が多くの式で構成されている場合は、波括弧を使うことができます。 def timesTwo(i: Int): Int = { println("hello world") i * 2 } 波括弧を使えるのは匿名関数でも同様です。 { i: Int => println("hello world") i * 2 } 匿名関数を引数として渡すときにこの構文が使われるのをしばしば見かけることでしょう。
「give someone some breathing room」は「〜に一息つかせる」という慣用句のようで、ニュアンスはわかるものの日本語にするのは難しいですね。。
(訳者追記)
匿名関数の例では全体を波括弧で囲んでいますが、その上のメソッド定義と同じように「=>」の後を波括弧にする書き方もできます。
val timesTwo = (i: Int) => { println("hello world") i * 2 } timesTwo(3)
その場で実行することもできます。
((i: Int) => { println("hello world") i * 2 })(3)
Partial application
アンダースコアを使って関数を部分適用することができます。 部分適用された別の関数が返されます。 def adder(m: Int, n: Int) = m + n val add2 = adder(2, _:Int) add2(3) // 5 最後の引数に限らず、引数リストのどの引数でも部分適用することができます。
(訳者追記)
defで定義したメソッドを関数オブジェクトにするときに「_」を使って以下のように書くことができますが
scala> val adderFunc = adder _
adderFunc: (Int, Int) => Int = <function2>
これは事前に一つも引数を渡さなない場合と同じ結果です。
scala> val adderFunc = adder(_,_) adderFunc: (Int, Int) => Int = <function2> scala> adderFunc(2,3) res7: Int = 5
Curried functions
いくつかの引数だけを適用して、残りの引数は後で適用するということを(関数の利用者に)させるのは時に理にかなっています。 これは掛け算で使う乗数・被乗数を関数の利用者側に構築してもらう関数の例です。 def multiply(m: Int)(n: Int): Int = m * n 最初の呼び出し時に乗数を確定し、後の呼び出し時に被乗数を渡します。 一度に両方の引数を直接渡してしまうこともできます。 multiply(2)(3) // 6 一つ目の引数を(2という固定値で)埋めて、二つ目の引数のみを受け取る関数をつくります。 val timesTwo = multiply(2)(_) timesTwo(3) // 6 ただし、カリー化は時にこんなクレイジーなコードにつながりかねません。 multiplyThenFilter { m: Int => m * 2 } { n: Int => n < 5 } この授業ではゆっくり時間をかけてこれらの概念にあなたを慣れさせていくことを約束します。 複数引数を受け取る関数であれば、以下のようなやり方でカリー化することができます。 前述の例を使ってやってみましょう。 def adder(m: Int, n: Int) = m + n (adder(_, _)).curried
(訳者追記)
最後に出てきたcurriedで作った関数は以下のように使います。
val curriedAdder = (adder(_, _)).curried curriedAdder(2) { val rand = new java.util.Random().nextInt(10) println(rand) rand } val add2 = curriedAdder(2) // または curriedAdder(2)(_) add2(3) // 5
カリー化とは、引数リストを一つずつに分割して「(A,B) => C」の関数を「(A) => (B) => C」にする事です。
言い換えると、引数はA一つだけ、戻り値として「(B) => C」の関数を返すように変えます。
Variable length argments
連続引数(Javaでいう可変長引数と同じです)を受け取るメソッドのための特別な構文があります。 def capitalizeAll(args: String*) = { ...
(訳者追記)
scala> def foo(arr: Int*) = arr foo: (arr: Int*)Int* scala> def p(arr: Int*) = arr foreach println foo: (arr: Int*)Unit scala> p(1,2,3) 1 2 3 scala> val arr = foo(1,2,3,4) arr: Int* = WrappedArray(1, 2, 3, 4)
配列とは別の型となります。
scala> val arr:Array[Int] = foo(1,2,3) <console>:11: error: type mismatch; found : Int* required: Array[Int] val arr:Array[Int] = foo(1,2,3) ^
Classes
以下の例は、defでメソッドを定義する、valを使ってフィールドを定義するといった例を含んでいます。 メソッドはまさにクラスの状態にアクセスできる関数ということになります。 class Calculator { val brand: String = "HP" def add(m: Int, n: Int): Int = m + n } val calc = new Calculator calc.add(1, 2) // 3 calc.brand // "HP"
(訳者追記)
Scalaでは何も指定しない場合、Javaでいうpublicになります。
Constructor
コンストラクタは特殊なメソッドという訳ではなく、メソッド定義以外のコード部分ということになります。 Calculatorクラス(前述の例)を拡張して、引数を受け取るコンストラクタを追加してその引数を内部状態の初期化に使うようにしてみましょう。 class Calculator(brand: String) { /** * A constructor. */ val color: String = if (brand == "TI") { "blue" } else if (brand == "HP") { "black" } else { "white" } // An instance method. def add(m: Int, n: Int): Int = m + n } 注釈:コメントは二つの異なるスタイルがあります
(訳者追記)
コンストラクタの呼び出しは「new Calculator("HP")」のようになります。
val cal = new Calculator("HP") println(cal.color)
別のコンストラクタが欲しいときはthisという名前のメソッドを定義します(補助コンストラクタと呼びます)。
クラス宣言の引数リストを持つコンストラクタを基本コンストラクタといい、最終的にこれを呼び出す形にする必要があります。
class Calculator(brand: String) { def this() = { this(null) } val color: String = if (brand == "TI") "blue" else if (brand == "HP") "black" else "white" def add(m: Int, n: Int): Int = m + n } val cal = new Calculator println(cal.color)
Expressions
Calculatorの例は、いかにScalaが式指向の言語であるかを示すものでした。 (例の中にある)colorという値はif/else式の結果を束縛します。 Scalaは高度に式指向であり、ほとんどのものが命令文ではなく式です。
if/elseだけでなく、matchやtry/catchなども文ではなく式なので最後に評価された値を結果として返します。
Inheritance
class ScientificCalculator(brand: String) extends Calculator(brand) { def log(m: Double, base: Double) = math.log(m) / math.log(base) }
コンストラクタ引数がある構造の場合は、受け取った引数を親クラスに渡さないといけないので要注意です。
Overloading methods
class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) { def log(m: Int): Double = log(m, math.exp(1)) }
Scala 2.9.1で試すと「error: overloaded method log needs result type」って言われるので、logメソッドの戻り値の型を明示する必要があります*1。
Traits
traitはクラス継承、ミックスインが可能なフィールドとふるまいの集合です。 trait Car { val brand: String } class BMW extends Car { val brand = "BMW" }
(訳者追記)
ミックスインは基本的にはwithを使いますが、上記の例のように継承する親クラスがない場合、一つ目のtraitはextendsで定義します。
また、traitには具体的な実装をもつことができます。
abstract class Value(value: Any) { override def toString = value.toString } trait Printable { def print() = println(toString) } class StringValue(str: String) extends Value(str) with Printable val str = new StringValue("foo") str.print // foo
Types
前述で例で数値の型としてInt型を受け取る関数を定義しました。 汎用な関数を定義して、あらゆる型を処理することもできます。 それは四角括弧を使った構文の型パラメータと呼ばれるものです。 型パラメータは複数取り入れることができます。以下は汎用なKeysとValuesを持つキャッシュの例です。 trait Cache[K, V] { def get(key: K): V def put(key: K, value: V) def delete(key: K) } メソッドに型パラメータを導入することもできます。 def remove[K](key: K]
(訳者追記)
Javaで同じものを書くと以下のようになります。
public interface Cache<K, V> { V get(K key); void put(K key, V value); void delete(K key); } public <K> void remove(K key) { ... }