@tako_programingの忘備録とか

@tako_programingの忘備録とかです。技術系の話が多くなるのかも。

Java初心者にScalaを勧める記事

LiTでJavaに慣れた人へScalaのススメ

LiTで、Androidコースやマインクラフトコースをやった人は必然的にJavaを触ることでしょう。そしてJavaを書いてる途中で色々と思うことがあるんじゃないでしょうか。めんどくせえな、と。行末にセミコロンを必ずつけなくてはいけないとかインスタンスを生成するときにコンストラクタの引数がなくても括弧をつけなくちゃいけないとかListのheadとtailを取ろうとするとクソめんどくさいとか関数合成がめんどくさいとか。そんな悩みをある程度解決してくれるのがScalaという言語。

Scalaって?

Java仮想環境上で動作する強い静的型付けの関数型オブジェクト指向プログラミング言語です。Javaに比べて簡潔で、関数型的な記述が可能です。また、Javaと同じオブジェクト指向言語でもありますし、それっぽい書き方がどうしてもできない時はJavaのような命令型っぽい書き方もできます。

環境構築から

OS Xならhomebrewですぐ入ります。

$ brew update
$ brew install scala
$ brew install sbt

3行目でインストールしているsbtというのはScalaのビルドツール(JavaでいうMavenとかGradleとかみたいな)です。sbtではJavaも使えます。また、pluginを入れることでKotlinやfrege等他の言語も動くみたいです(確認はしてません)。

REPL

ScalaにはREPL(Read-Eval-Print-Loop)があります。REPLは、ターミナルで

$ scala

と入力するだけで起動します。さっそく使ってみましょう。

scala> 1 + 2

と入力してみてください。

res0: Int = 3

と出力されるはずです。これの動きは見たまんまなのでわかりますね。1と2を足してるんです。こんな感じでREPLはちょっとしたコードの動きを確かめたりするのに向いています。とりあえず今回は、このREPLを使ってScalaに軽く触れてみましょう。

変数

Scalaでは変数の宣言と代入はこのように行います。

val n: Int = 3

これはJavaだと

final int n = 3

このようになります。Scalaではval or var+ 変数名+:+型名+=+という形で宣言と代入ができます。ここでまず意味がわからないのはvalでしょう。その後のJavaの例でわかったかもしれませんが、valには宣言した変数が再代入不可になるという意味があります。試しにvalで宣言した変数に再代入してみましょう。

scala> val n: Int = 3
n: Int = 3
scala> n = 4
<console>:12: error: reassignment to val
       n = 4
         ^

エラーが起きていますね。再代入不可だからです。ここで、もう一つ不思議に思った点があるんじゃないでしょうか。なんでこんな序盤でJavaではあまり使わないfinal修飾子のようなものを例として使ってきたかという点です。Scalaでは基本的に変数の再代入を行いません。valの代わりにvarを使うことで、再代入を許すこともできますが、valを使う方が推奨されています。これはなぜかというと、再代入が可能だと状態の管理が難しくなってしまうからです。コードの中のどこで変数の中の値が変わっているかわからない状態というのはとても恐ろしいです。思わぬ例外を引き起こす可能性があります。例えばどこかでnullでも入ってたらJavaを書いたことがある人ならよく目にするであろうjava.lang.NullPointerExceptionが連発することでしょう。しかもIDEは例外が起こった場所は示してくれるかもしれませんが、変数を再代入した場所はしめしてくれないでしょう。そういった理由でScalaでは変数は極力valで宣言し、再代入を禁止することが推奨されているのです。

関数

次は関数です。ではまず簡単な関数を1つ書いてみましょう。

scala> def add3 (x: Int): Int = x + 3

この関数は引数として整数を1つとりそれに3を足して返す関数です。Scalaで関数を定義する時はdef+関数名+引数リスト+:+返り値型+=+処理となります。ではこの関数を使ってみましょう。

scala> add3(1)
res0: Int = 4

Scalaで関数を使う時はJavaと同じように関数名の括弧の中に引数をいれます。ちなみに引数のない関数は括弧を省略できます。

scala> def printHello ():Unit = println("HELLO")
printHello: ()Unit

scala> printHello
HELLO

ここで、関数の返り値に指定されてるUnitというのはJavaでいうvoidのようなものです。次は複数の引数を持つ関数を定義しましょう。基本的にはなにも変わらないです。,で引数を区切るだけです。

scala> def add (x: Int, y: Int): Int = x + y
add: (x: Int, y: Int)Int

scala> add (1, 3)
res2: Int = 4

こんな感じです。ちなみに関数が複数行にまたがる時は波括弧で囲います。

def add(x: Int, y: Int): Int = {
  x + y
}

ここまできてJavaプログラマーは「あれ?」と思った所があると思います。そうです、returnがないんです。Scalaでは関数の最後の結果が自動的に返り値になります。もちろんreturnで明示的に返り値を指定することもできますが、普通はつけなくてよい所ではreturnは付けないようにします。

関数の変数

Scalaでは、関数を第一級オブジェクト(ファーストクラスオブジェクト)として扱っているので、変数への代入が可能です。

scala> def add3 (x: Int): Int = x + 3
scala> val f: Function[Int, Int] = add3 _

これで、変数fにadd3が代入されました。実行してみましょう。

println(f(3)) //=> 6

6が出力されると思います。ちなみにScalaではFunction0~Function22というトレイト(Javaでいうインターフェース)が用意されているので、22を超える引数の関数を代入しようとするとエラーが起こります。また、関数を変数に代入する時には_が必須です。Scalaでは関数が普通のオブジェクトなので、他の関数の引数に関数を渡せたり、関数が関数の返り値にできたりします。このような関数をScalaに限った話ではないですが、高階関数と呼びます。

関数の部分適用

Scalaでは2つより多い引数を持つ関数にその引数の数より少ない数の引数を与えると、残りの引数を引数にとる関数を返す機能があります。何を言ってるのかわからないですね。下の例をみてください。

scala> def add (x: Int, y: Int): Int = x + y

scala> val f = add(3, _: Int)
  f: Int => Int = <function1>

scala> f(4)
  res0: Int = 7

これには関数の部分適用が使われています。まず3行目でadd関数に3と_プレースホルダー)を与えます。プレースホルダーは未確定の変数を意味します。すると機能的には、先程から使っているadd3関数と同等の関数がfに代入されます。そしてfは普通の関数のように残りの引数を与えて呼び出すことができます。

関数合成

ScalaではJavaと同様に関数合成をすることができます。

scala> def add3(x: Int): Int = x + 3

scala> def add5(x: Int): Int = x + 5

scala> add3(add5(1))
  res0: Int = 9

scala> def f (x: Int): Int = add3(add5(x))

愚直に書くとこうなります。意味はわかりやすいと思います。add5に1を渡して6が返ってきますね、その6をadd3に渡すと9が返ってきます。それをそのまま1つの関数にすることもできます。でも次は少しスマートに書いてみましょう。それにはFunctionオブジェクトのcomposeメソッドを使います。

scala> val add8 = (add3 _) compose (add5 _)

scala> println(add8(0))
8
Function<Int, Int> add3 = x -> x + 3
Function<Int, Int> add5 = x -> x + 5
Function<Int, Int> add8 = add3.andThen(add5)

このすぐ上のコードは(わかるとは思いますが)Javaで同じことをしたものです。可読性で言えば大差ないかもしれないですが、Scalaの場合はComsumerやPredicateのようないくつかの標準関数型インターフェースがあるわけではなく言語自体が関数をオブジェクトとしてサポートしているので、複雑になっていけばより(Javaと比較して)コードが読みやすいものになってくると思います。あと今回は両方とも足し算するだけの関数なので順番はあまり関係ないのですが、順番が関係ある場合、composeメソッドだと呼び出すときに適用する順番と逆の順番で書くことになると思います。それを避けて順序どおりに書きたいときは(JavaのFunction同様に)andThenメソッドをcomposeの時と同じようにに使うといいです。

再帰関数

話を少し変数の所に戻します。先程も言ったとおりScalaの変数は基本的にvalを使います。じゃあこんなJavaArrayListの総和を求めるコードをScalaでvarを使わずに書きたいときはどうしますか?

public int accumulate( ArrayList<Integer> arrayListA ){
        int sum = 0;
        for( Integer tmp : arrayListA )
            sum += tmp;
  
        return sum;
}

少し悩むと思います。そしてここで登場するのが再帰関数です。再帰関数とは関数の中で自分を呼ぶ関数のことです。さきほどの配列の総和を求める例で見てみましょう。

def total(List[Int]: xs): Int = if (xs.isEmpty) 0 else xs.head + total(xs.tail)

あら簡単。一行で書けちゃいました。ちなみにxs.headでListの先頭要素を、xs.tailで、Listの先頭以外の要素を取得します(これ自体もJavaでやろうとすると結構めんどくさいですよね)。このように再帰関数を使うと簡単に綺麗に関数が書けるようになります。

終わりに

今回はJavaとくらべてScalaが良いぞって話だったので、Javaと書き方に大きな差のない基本構文(ifなど)は紹介しませんでした。なので、この記事を読んでいきなりScala書けやと言われても到底ムリな話です。ですが、この記事を読んだLiT出身のJavaプログラマーJavaの、主に関数周りの煩わしさから開放される1つの手段としてScalaのことを考えてくれるようになったのであればそれは自分にとってすごく嬉しいことです。Scalaは今は主にPlayFrameWorkやScalatraといったフレームワークWebのサーバーサイドで使われている言語ですが、Androidアプリ開発もできないことはありませんし、Scala.jsというライブラリでJavaScriptコンパイルができるのでAltJSとしてWebフロントエンドの開発に貢献できるようになるかもしれません。(Androidアプリ開発をするならKotlinという言語を断然オススメします。)