• ログインログイン
  • 新規登録新規登録

MENU

流行の技術に実際に触れてみよう 「Scala」と「PlayFramework」(3)

連載流行の技術に実際に触れてみよう 「Scala」と「PlayFramework」

ScalaとはJVM上で動作する関数型オブジェク指向言語です。簡潔な文法と実効速度に定評があり、国内でも徐々に浸透してきています。 流行の技術に実際に触れてみながら、動くものを作っていく開発レシピです。

前回までのプログラミング学習コラムに続き3回目の勉強内容です。

前回はJavaと比較しながら、Scalaのオブジェクトを中心に特徴や機能を紹介させて頂きましたが、今回はScalaの関数型言語としての機能を見ていきたいと思います。

Scalaの関数型言語の機能

関数型言語機能入門

関数型の基本的な考え方には、すべての計算は引数及びその戻り値を基本とする関数の評価によってのみ行われることです。その中で大切な概念になってくる、2つの概念を今回は紹介できればと思います。

  • 関数は値
  • 副作用のないプログラミング

関数は値

関数が値を聞いて、ピンと来ない方が多いのではないでしょうか・・・ あるいは他の言語にある高級関数やクロージャーを思い浮かべた方もいるのではないでしょうか。関数型プログラミングでは関数を整数や文字列などの基本オブジェクトと同等のものとして捉え、値として扱います。

まずは関数を値として扱うサンプルを読んでみましょう。

object Sample{
 def main(args: Array[String]) = {
 // double に 2倍する関数を代入
 val double = (value:Int)=>value * 2
 // doubleに入っている関数を呼び出す
 println(double(10))
 //関数myLoopの二番目の引数にdoubleを渡す
 myLoop(10,double)
 //二番目の引数に3倍する関数をわたす
 myLoop(10,(value:Int)=>value * 3)
 //引数に関数を返す関数を渡す
 myLoop(10,nTimes(5))
 }
 def myLoop(max:Int,func:(Int) =>Int ) = {
 for(i <- 1 to max)
 println(func(i) + " ")
 println
 }
 def nTimes(times:Int):(Int) =>Int ={
 (value:Int) => value * times
 }
}

まだ、紹介させて頂いていない言語仕様が幾つか出ていますが、要は関数を普通の値のように返り値や代入したりすることができるというものです。

副作用の無いプログラミング

副作用とは何でしょうか?プログラミングにおける副作用とはある処理が状態の変更を引き起こし、その後の処理の結果に影響を与える事を言います。

具体的には「全ての計算は引数及びその帰り値を基本とする関数の評価によってのみ行われること」を指します。 これを徹底して行うと関数を実行するごとに実行結果が異なるということは起きなくなります。

例えば、前回の整数の四則演算を例にすると整数オブジェクトの四則演算子関数は副作用がない関数です。

val three = 1 + 2

これは整数オブジェクトが持つ+関数を呼び出して、1+2の演算を行いその結果を返すというものです。何度実行しても1+ 2は3です。内部の状態も変更しません。このような処理が副作用のない処理です。副作用のある処理として、例えばファイルアクセスの処理、プロパティのアクセッサなどが挙げられます。

要はメンバやグローバル変数の値を変更する処理のことを指します。例えば、関数1でファイル書き込みを行い、関数2でファイル読み込みを行い、その結果を出力するという処理は実行するたびに結果が違います。

def write = {
 //ファイル書き込みの処理色々
 // 文末にhogeを追加する処理
 //.…….
 //ファイルを返す
 return file
}
def read(file:File) = {
 //ファイルをテキストに読み込む処理色々
 //.……..
 return text;
}
val file = write
val text = read(file)
println(text)

 なぜ副作用が好ましくないのか?

以下のコードを読んでみましょう。

object Sample{
 var message = "HelloWorld"
 def main(args: Array[String]) = {
 someFunction()
 output()
 }
 def output = {
 println(message)
 }
 def someFunction = {
 // 色々な処理が入ります。
 ///.....
 }
}

以上のコードの出力結果は多くの人は HelloWorld と予想するのではないかと思います。ところが、仮に someFunction の中で message を変更する処理が入っていると, output の実行結果は変化してしまいます。

つまり、 output のコードの結果を解読するには someFunction のコードも読む必要が出てくるのです。これの何が問題かというと、すごく複雑です。複雑なコードは修正しずらく、読みづらく、理解しづらいです。

もちろん、複雑であるべきコードは複雑であるべきなのですが、これはただ画面に移すだけですでとても単純な処理です。常に HelloWorld と出力するつもりしか無い場合であれば、 val message = "HelloWorld" と var からval に変えてあげるとすごく単純になります。

この 一度しか代入できない事を単一代入と呼びます。JavaやPHPなどでもfinalやdefineを使うと単一代入することができますが、関数型言語は 単一代入がオプションではなく、デフォルトで実装されていて、従来の変数を使うことができないことが多いです。(Haskell、Lispなど)

この考え方は並列処理や遅延評価といった関数型の機能と大きく繋がります。

for関数

Scalaのfor文はJavaやPythonのものとはかなり異なるので、ここで紹介しておきます。

  • 定番のfor文
// Sample.java
for(初期化;条件;継続処理){
}
for(型 変数名:コレクション){
}
// Sample.scala
for(変数名 <- コレクション){
}

基本的にはこれだけなのですが、値の繰り返し方若干異なります。

  • コレクションの生成
// Sample.scala
for( i <- 1 to 100){
 //1から100
}
for( i <- 0 until 5){
 //0から5つ、01234
}
//繰り返すだけならば、iは使わなくてok
for( _ <- 1 to 100){
 println("HelloWorld")
}

1 to 100 などが出ていますが、これは一体何を返しているのでしょうか?という事でScalaREPLから以下のコマンドを叩くと結果を見られます。

scala> 1 to 100

結果

res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)

呼ばれるたびにコレクションを生成しすることがわかりました。

  • 多重ループ
// Samle.java
for( int i = 0;i < 100;i++){
 for(……){
 }
}
// Sample.scala
for( i <- 1 to 100; j <- 1 to 100){
}

Scalaは多重ループを1行に書くことができるので簡潔に記述することができます。

  • フィルター
// Sample.java
for(int i = 0;i< 100;i++){ 
 if(i % 2 ==0 && i % 3 == 0){
 //処理
 }
}
// Sample.scala 
// for(変数名 <- コレクション if節;if節…){}
for(i <- 1 to 100 if i % 2 == 0;if i % 3 == 0){}

Javaの場合、for文本体に書かなければなりませんでしたが、Scalaは簡素に書くことができます。

  •  ジェネレータ 値を返すfor関数になります。
// Sample.scala
val list = for( i <- 1 to 100 if i % 2 == 0 ) yield {
 i.toString
}
for( str <- list){
 println(str)
}

ジェネレータ文は for文内に yield を含むことにより値を返すforを作ることができます。yieldをつけた処理本体が一つの値を返す処理ブロックとして扱われ、最終的にfor文全体の値としてコレクションを返します。

まとめ

Scalaの関数型言語としての機能を見てきました。今回は高級関数と副作用を中心に紹介させて頂きましたが、他にも遅延評価やパターンマッチ、並行プログラミングなど様々な便利な特徴を持っています。

[おまけ] fizzbuzzをScalaで書いてみました

object FizzBuzz
{
 def fizzbuzz( num:Int ):String = {
 val fizz: Int => String = { n => if( n % 3 == 0 ) "fizz" else "" }
 val buzz: Int => String = { n => if( n % 5 == 0 ) "buzz" else "" }
 val number: ( Int, String ) => String = { ( n, s ) => if( s.isEmpty ) n.toString else s }
 number( num, fizz( num ) + buzz( num ) )
 }
 def main( args:Array[String] ):Unit = {
 for( i <- 1 to 100 ) println( fizzbuzz( i ) )
 }
}

オススメ記事一覧

もっと見る
完全無料!

1で登録完了!

エンジニアの仕事・年収や選考ノウハウ記事が読めるほか、
会員にはプログラミング講習やES・面接対策などリアルな無料サポートも充実。
ここだけの求人情報も多数。

今すぐ新規会員登録
Page Top