Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scalaの複数変数定義とjava.util.Scannerを組み合わせると一気に値を読み取れて便利でかなりUNIX哲学を感じる

昨日、FOLIOさんのオフィスでイベントがあった。めちゃくちゃ行きたかった!!! くやしい!!! のだが、資料がアップされてきているのでホクホクだ。ありがたい。

そんな中でこのような資料があった:

資料の本筋からは脱線するのだが、この中で以下のような記述があった:

val sc = new java.util.Scanner(System.In)
val n, t, a = sc.nextInt()

この記述には唸ってしまった。このコードを実行すると、ユーザが入力した行ごとにIntが読み込まれ、それぞれntaにアサインされる。Scala、こんなことできるのか!

1
2
3
// => n = 1; t = 2; a = 3

タネ明かしをしよう(人のコードなのに・・・)。このコードは、Iteratorの特性と、Scalaの構文の妙がうまく噛み合ったクールなコードだ。

Iterator

他のたいていの言語がそうであるように、ScalaにもIteratorが実装されている。ScalaのIteratorの実装はJavaの標準ライブラリのIteratorとは独立しているが、基本的なコンセプトは同じだ。

ちょっとした座学をしよう。Iteratorは、next()hasNext()を実装するだけで成り立つ便利なコレクションだ。たった2つのメソッドから成り立つのに、驚くべきほど強力な抽象を提供してくれる。Iterator[A]IterableOnce[A]IterableOnceOps[A, Iterator, Iterator[A]]のサブクラスだ。つまり、「一度だけ値を走査できる」ものの仲間だ。

このIterableOnceOpsが素晴らしくって、驚くほど多くのオペレーションを行うことができる。

www.javadoc.io

mapflatMapもできるし、foldforAllもある。たった2つのメソッドを実装するだけで、データの集まりが突然豊穣な道具箱に変貌するのだ。

さて、Iteratorを褒めたところで、それ自体の基本的な使い方は、next()して次のデータを取り出すことだ。

val it = Iterator("All", "Your", "Base")
it.next() // => "All"
it.next() // => "Your"

この機能はJavaのIteratorでも同じで、next()hasNext()がある。java.util.Scannerも、Iteratorを継承しているからIteratorの仲間だ。

複数定義

さて、Scalaにはあまり知られていない便利構文が存在する。複数のvalを同時に定義する機能(特に決まった名前はない)だ。

使い方は簡単、ただval x, y, z = ...のように、変数名をカンマでつないで並べるだけだ。これは、val x = ... val y = ... val z = ...と並べて書いたのと等価だ:

val x, y, z = 42
// is equivalent to:
val x = 42
val y = 42
val z = 42

これは複数の変数の初期値を定めるのに便利だ。この構文はvarでも使えるから、動的計画法なんかでガチャガチャダイナミックに値をいじるときに重宝しそうだ。

仕様は以下の通り:

scala-lang.org

(4.1 Value Definitions)

A value definition $ \textsf{val} \, p_1, ..., p_n = e $ is a shorthand for the sequence of value definitions $ \textsf{val} \, p_1 = e; \quad ...; \quad \textsf{val} \, p_n = e $.

合体!!

ここで互いの真価が発揮される!Iteratornext()は不純な、つまり純粋ではない、つまり副作用を有している、つまり呼ぶたびに状態が変化して実行環境に影響を与え、結果が変わりうる関数だ。

val sc = java.util.Scanner(System.In)

val x, y, z = sc.nextInt()

するとvalで値を束縛するたびに違う値がやってくるから、それぞれに呼び出した順に違う値がやってくる。java.util.Scannerの場合は、Scannerが値を読み取った順で、変数の値を拘束できるのだ!

上掲のコードは、以下に示すコードと等価だ!

val sc = java.util.Scanner(System.In)

val x = sc.nextInt()
val y = sc.nextInt()
val z = sc.nextInt()

これは本当にクールな組み合わせだ。Scalaがこのシンプルな記法(とそれに対する展開ルール)を用意していることで、また別のシンプルなツールであるIteratorとの組み合わせにより、非常に面白い使い方ができるのだ。シンプルとシンプルが相互作用してうまく動いているのを見るのは、大興奮だ!

余談

ちなみにこれだとリストやタプルの値を拘束したいときにどうするの?と聞かれそうだが、心配は無用、リストやタプルのパターンをそのまま書けば良いから、先程までの記法とは衝突しない:

val List(x, y, z) = lis // List()で書く
val (t1, t2, t3) = triple // カッコでくくる
★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?