Scalaに入門してみた その一

HHKに久々に戻ったら、HHKのコンパクトさが一層目立つようになりました。まぁちょっと窮屈に感じてしまうのは仕方無いですが、キータッチと、HHKにかなり特化させたキー配列にしているおかげで、Kinesisよりも打鍵速度自体はずっと早いですね。疲れ自体も何故かKinesisとあまり変わらないのは、私のKinesis使うときの姿勢が問題だったからでしょう。

一年に一つは新しい言語を勉強しよう、というのをどこかで聞きました。新しい言語を勉強するというのは、新しいパラダイムを勉強するということでもあるということで、トレンドを追い続けないといけない日常とは別に学ぶのも大事かと思います。

Scalaの名前自体は結構前から知っていました。2年位前から見た記憶がありますが、いかんせん二年前とかはデスマの真っ最中で、仕事以外をしていた記憶がかなり曖昧というか無いというか。
まぁそれは置いておいて、私がScalaを知った流れとしては、JRubyとかJPythonとか、JVM上で動く言語が実用的になってきて、それに繋がる流れで話題になっていたような気がします。

それから時は流れて、最近はOCaml以外まともに、OCamlでさえもまともな使い方ができているかよーわからん私ですが、それでも関数型言語に触れたことで、それ以外の関数型言語にも食指が動くようになりました。特にScalaは転職候補でも使われようとしているということで、ここでいっちょ勉強だけでもしておこうかと思った次第です。えぇ、今回は興味よりかは実用が勝りました。

とりあえず一通り勉強したメモを書き連ねます。なお、参考にしたサイトなどは以下のサイトです。

Scalaプログラミング入門
@ITのScala入門
また、文中の説明書きのようなものは、基本的に私の理解で書いてあるため、間違いとかが色々あると思いますので、その辺は容赦ください。

Scalaの特徴

色んなところに書いてあるのでまとめるだけ。

  • JVM上で動作する、「関数型オブジェクト指向言語
    • Twitterかどこかで「関数型の皮を被ったJava」というのを見た記憶が・・・
  • Javaのクラスライブラリをそのまま使える
    • 多分これが一番大きいんではないかと。Javaのクラスライブラリを使えるというだけでも十分強力に思えます。
  • 最近色んなところで応用事例を見かける
    • Twitterで使われているというのは有名
    • Better Javaとして使えるから楽というのも見た記憶が
  • 冗長になりがちなJavaより簡便に書ける言語仕様?

などなど。

基本的な文法

基本的な文法をまとめます。基本だけですし、参考サイトのほうがもっとすっきりまとまっているかと思います。なお、最近OCamlばっか触っているのと、同じ関数型言語にカテゴライズされている関係上、OCamlとの比較が多いですがご了承下さい。メモですんで。

***変数

var hige:String = "foobar"     # 変数の型指定あり
var hoge = "hoge"              # 変数の型を推論してもらう
var foo,bar = "foobar"         # 複数の変数を単一の値で初期化する
var foobar:String = _          # デフォルト値

var 変数名 = 初期値で変数が作れます。また、型を指定した状態でかつ _ を指定すると、それぞれの型の初期値を代入することができます。初期値は色々ありますが、基本的には0とかです。Stringは初期値がnullなのでちょっと・・・。

***定数

val constant = 100
val hoge:String = "hoge"

この辺ですでにOCamlとかとは感覚が違いますが、val 定数名 = 初期値 で定数が作れます。定数なので、再代入しようとするともれなくコンパイルエラーになります。OCamlとかHaskellだとそもそも「束縛」でしかないですので。

***リテラル

100         # Int
10000000L   # Long
1.23        # Double
1.234f      # Float
'h'         # Char
"string"    # String
false       # Boolean
'symbol     # Symbol

基本的にJavaそのままっぽいです。シンボルリテラルというのがJavaにないものですが、これはRuby1.9以降のシンボルと同じと考えてよさそうです。

***配列、リスト、Range

val array = new Array[Int](10)    # Intのデフォルト値で埋められた要素10の配列
var array2 = Array(1,2,3)         # Int型の3要素配列
array2(0)                         # Javaでのarray2[0]と同義
array2(0) = 100                   # 配列の要素に代入
val list = List(1,2,3)            # Int型のリスト
val list2 = 1 :: 2 :: 3 :: Nil    # 関数(::)だけで構築したリスト。最後のNilは必須
val range = 1 to 3                # Int型の1から3までのRangeオブジェクト
range.toList                      # 1から3までのリスト

配列はnewで作るか、それともArrayだけで作るか、の二択がとりあえずあるようです。new Arrayでやる場合、型をちゃんと指定しないとえらいことになります。リストは、糖衣構文があまり無いようで、最後にNilを付けないとならんあたりがちょっと脱力ポイント?でしょうか。
また何げなく出ていますが、new Array[Int](10)の[Int]の部分が、型パラメータを具体化している部分です。型パラメータの指定に[]が使われているため、配列のアクセスに()を使うようになった感じがとってもしますね。
また、Rubyの..みたいな感じで、x to yでxからyまでの幅を持ったRangeというクラスのオブジェクトが作れます。リテラルだと数値にしか使えなさそうですが、手軽に作れるというのは魅力的です。OCamlにも欲しいです、個人的に。

***制御構造

val x = 100
if (x == 100) {
  println("hoge")
} else {
  println("huga")
  "hogehoge"
}
if (x == 101) println("hoge")   # elseが無く、条件がマッチしないのでUnitが返される

val x = List("hoge", "hige", "foobar")
for (item <- x) println("item = " + item)   # リストの各要素について処理
for (item <- x                              # リストの各要素のうち、if以降の条件に一致するものだけを処理
     if item.length < 5
    ) println("item = " + item)

while(true) {
  println("hogehoge")
}
do {
   println("hogehoge")
} while (true)

この辺は他の言語と変わらないため、さっくりと。一番異彩を放つのはfor文ですが、基本的にはforeach相当の使い方とみてよさそうです。また、list comprehensionみたいに、filterと呼ばれる条件文を書くことができます。このfilter、複数書くことができ、複数書いた場合はすべての条件に該当する要素だけが処理の対象となります。
まぁ、リストとかの場合はmapとかfilterとかがそもそも用意されているので、そっちを使う場合がほとんどでしょう。

***関数

def test(n:Int) :Int = n * 3    # 基本的な関数の定義
test(3) --> 6
def test1(n:Int) = n * 2        # 戻り値の型を型推論に任せる
test1(2) --> 4
def test2(n) = n + 1            # 引数の型を省略することはできない

var f = (x:Int) => x * 2        # 無名関数。やっぱり引数の型は省略できない
List(1,2,3).map(x => x * 2)     # 高階関数として渡す場合、型指定はしなくてもいい
List(1,2,3).map(_ * 2)          # こんな形の無名関数もできる
 --> List(2,4,6)
var f = (_:Int) * 2             # このような宣言でも作れる

def add(n1:Int, n2:Int) = n1 + n2
(add(_:Int, 1))(3) --> 4        # 部分適用

def count(start:Int, end:Int, step:Int = 1) = {
  var c = start
  while (c < end) {
    c += step
  }
  c
}
count(1, 100)    # stepはデフォルトとして1を使える

関数型言語と呼ばれるからには関数が宣言できなければなりません。基本的にはdef 〜で宣言して利用する感じです。また、複数行にわたる宣言の場合、def hoge() = {} のように{}で内容を囲む必要があります。私がひっかかった点として

  • 引数の型は省略できない?
    • エラーから見るに文法的に省略できないっぽいです
  • 無名関数単体で作る場合には、引数の型を省略できない
    • 高階関数として使う場合は無くても、渡した時点で確定できるので不要となるようです
  • 部分適用がいささかわかりづらい
    • ような気がする、といったくらいの感じです。一々プレースホルダを書かないとならないのがちょっと使いづらそうです。

こんな感じです。無名関数の定義はOCamlよりずっとすっきりしていますね。プレースホルダ(_)を引数として使える、というのを見たとき、何故かPerlの$_を思い出してしまいました。用途は違いますが。
関数にはデフォルト値を指定できますが、デフォルト値の指定として、引数名 = 値として渡すことができ、この辺はLabeld interfaceっぽいです。

***パターンマッチ

def test(n:List[Int]) :Int =
  n match {
    case Nil => 0
    case x :: _ => x
  }
test(List(1,2,3,4))  -->  1
test(List())         -->  0

def test1(n:Any) =
  n match {
    case i:Int if i >= 100 => 100
    case s:String => 200
    case _ => 300
  }
test1(1)      --> 100
test1("hoge") --> 200

最近の関数型言語に必須の機能のパターンマッチも当然のように用意されています。case 〜でやるため、switchの代替のような気分になってきますが、基本的にOCamlと一緒です。親クラスを指定すると、instanceofのようなこともできるようです。こっちの方がinstanceofのif文を並べるよりずっとわかりやすいですね。
_ はワイルドケース(でいいんでしたっけ)となっており、型でマッチさせたいだけだったりして、パターンマッチ時に値が存在することだけが重要な場合に使えます。

ひとまずまとめ

まだまだ基本的な部分だけしか学習できていませんが、これだけでも知っておけば、REPLで色々と実験できます。実際書いている間にも色々と試したりしてます。ScalaのREPLはかなりパワフルなようで、結構色々とできるようです。
まだ感想を書くほどまとめきってませんので、オブジェクト指向的な面も学習してから、再度Scalaという言語についての雑感としたいと思います。