Copyright © 2014 TIS Inc. All rights reserved.
再帰で  
脱Javaライク
2015.2.21
TIS株式会社	
  
前出祐吾
Copyright © 2014 TIS Inc. All rights reserved. 2
自己紹介
TIS株式会社 戦略技術センター!
社内向けエンジニア基盤の整備

      ▶ Scalaの活用検証!
甲賀忍者!
東京デビュー(先月)!
@yugolf
Copyright © 2014 TIS Inc. All rights reserved. 3
Scalaってどうなの?
コード量が半分くらいに!	
  
!
とはいえ、Scalaって難しいんじゃないの?	
  
!
大丈夫、Javaのライブラリ呼べるし、Javaライクにも書ける
し。	
  
(その場合、コード量は半分にならないけど。。)	
  
!
ん!?
Copyright © 2014 TIS Inc. All rights reserved. 4
脱Javaライクしてファ
ンクショナルなプロ
グラマーになるんです。	
  
Copyright © 2014 TIS Inc. All rights reserved. 5
Javaライクってなんですの? 例:総和
1からnまでを全部足す。	
  
!
(めいっ	
  
ぱい、	
  
Javaっぽ	
  
く足す)	
  
def	
  sowa(value:	
  Int):	
  Int	
  ={	
  
	
  	
  var	
  result	
  =	
  0;	
  
	
  	
  for(n	
  <-­‐	
  1	
  to	
  value)	
  {	
  
	
  	
  	
  	
  result	
  =	
  result	
  +	
  n;	
  
	
  	
  }	
  
	
  	
  return	
  result;	
  
}	
  	
  	
  	
  
!
sowa(10)	
  
//>	
  res4:	
  Int	
  =	
  55
Copyright © 2014 TIS Inc. All rights reserved. 6
脱Javaライク
varはやめよう。	
  
forループもやめよう。	
  
再帰にしよう。
①終了条件 ②自分呼出 ③計算
Copyright © 2014 TIS Inc. All rights reserved. 7
再帰にしよう:ステップ①
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
①終了条件 ②自分呼出 ③計算
計算対象の数値が0に
なったとき
Copyright © 2014 TIS Inc. All rights reserved. 8
再帰にしよう:ステップ②
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
①終了条件 ②自分呼出 ③計算
自分()
Copyright © 2014 TIS Inc. All rights reserved. 9
再帰にしよう:ステップ③
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
①終了条件 ②自分呼出 ③計算
対象値 + 自分()
Copyright © 2014 TIS Inc. All rights reserved. 10
再帰にしよう:実行1
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
①終了条件 ②自分呼出 ③計算
sowa(10)	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  
//>	
  res6:	
  Int	
  =	
  55	
  
Copyright © 2014 TIS Inc. All rights reserved. 11
再帰にしよう:実行2
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
sowa(100000)	
  
//>	
  java.lang.StackOverflowError	
  
//|	
  	
   at	
  Demo$$anonfun$main$1.sowa$1(Demo.scala:64)	
  
//|	
  	
   at	
  Demo$$anonfun$main$1.sowa$1(Demo.scala:64)	
  
//|	
  	
   at	
  Demo$$anonfun$main$1.sowa$1(Demo.scala:64)
①終了条件 ②自分呼出 ③計算
Copyright © 2014 TIS Inc. All rights reserved. 12
スタックオーバーフロー!?
スタックのサイズを大きくする? (-Xss)	
!
 スタックの消費量を減らそう!
Copyright © 2014 TIS Inc. All rights reserved. 13
スタックオーバーフロー対策
末尾再帰で最適化	
!
!
!
!
自分を呼び出したあとに計算(足し算)するのではなく、	
計算したものを引数として、その結果を蓄積する。	
!
戻ってきたあとに計算がなく、呼出し元に戻るだけ。
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
Copyright © 2014 TIS Inc. All rights reserved. 14
末尾再帰
アキュムレータ
(accumulator)
自分を呼び出したあとに計算(足し算)するのではなく、	
計算したものを引数として、その結果を蓄積する。
+	
  3	
  =
+	
  2	
  =
+	
  1	
  =
Copyright © 2014 TIS Inc. All rights reserved. 15
末尾再帰
アキュムレータ
(accumulator)
自分を呼び出したあとに計算(足し算)するのではなく、	
計算したものを引数として、その結果を蓄積する。
①終了条件 ②自分呼出 ③アキュムレータ	
  
 	
  による計算
Copyright © 2014 TIS Inc. All rights reserved. 16
末尾再帰にしよう:ステップ①
def sowa(value: Int, acc: Int = 0):Int =	
if(value == 0) acc	
else sowa(value - 1, value + acc)
計算対象の数値が0に
なったとき
①終了条件 ②自分呼出 ③アキュムレータ	
  
 	
  による計算
Copyright © 2014 TIS Inc. All rights reserved. 17
末尾再帰にしよう:ステップ②
def sowa(value: Int, acc: Int = 0):Int =	
if(value == 0) acc	
else sowa(value - 1, value + acc)
自分()
①終了条件 ②自分呼出 ③アキュムレータ	
  
 	
  による計算
Copyright © 2014 TIS Inc. All rights reserved. 18
末尾再帰にしよう:ステップ③
def sowa(value: Int, acc: Int = 0):Int =	
if(value == 0) acc	
else sowa(value - 1, value + acc)
対象値 + アキュム	
  
      レータ
①終了条件 ②自分呼出 ③アキュムレータ	
  
 	
  による計算
Copyright © 2014 TIS Inc. All rights reserved. 19
末尾再帰にしよう:実行
def sowa(value: Int, acc: Int = 0):Int =	
if(value == 0) acc	
else sowa(value - 1, value + acc)
sowa(100000)	
  
//>	
  res4:	
  Long	
  =	
  705082704
①終了条件 ②自分呼出 ③アキュムレータ	
  
 	
  による計算
Copyright © 2014 TIS Inc. All rights reserved. 20
逆アセンブル
public	
  final	
  int	
  sowa(int);	
  
	
  Code:	
  
	
  	
  	
  	
  0:	
  iload_1	
  
	
  	
  	
  	
  1:	
  iconst_0	
  
	
  	
  	
  	
  2:	
  if_icmpne	
  	
  	
  	
  	
  9	
  
	
  	
  	
  	
  5:	
  iload_1	
  
	
  	
  	
  	
  6:	
  goto	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  18	
  
	
  	
  	
  	
  9:	
  iload_1	
  
	
  	
  	
  10:	
  aload_0	
  
	
  	
  	
  11:	
  iload_1	
  
	
  	
  	
  12:	
  iconst_1	
  
	
  	
  	
  13:	
  isub	
  
	
  	
  	
  14:	
  invokevirtual	
  #101	
  
	
  	
  	
  	
  	
  	
  	
  //	
  Method	
  sowa2:(I)I	
  
	
  	
  	
  17:	
  iadd	
  
	
  	
  	
  18:	
  ireturn
public	
  final	
  int	
  sowa(int,	
  int);	
  
	
  Code:	
  
	
  	
  	
  	
  0:	
  iload_1	
  
	
  	
  	
  	
  1:	
  iconst_0	
  
	
  	
  	
  	
  2:	
  if_icmpne	
  	
  	
  	
  	
  7	
  
	
  	
  	
  	
  5:	
  iload_2	
  
	
  	
  	
  	
  6:	
  ireturn	
  
	
  	
  	
  	
  7:	
  iload_1	
  
	
  	
  	
  	
  8:	
  iconst_1	
  
	
  	
  	
  	
  9:	
  isub	
  
	
  	
  	
  10:	
  iload_1	
  
	
  	
  	
  11:	
  iload_2	
  
	
  	
  	
  12:	
  iadd	
  
	
  	
  	
  13:	
  istore_2	
  
	
  	
  	
  14:	
  istore_1	
  
	
  	
  	
  15:	
  goto	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  0
ただの再帰 末尾再帰
Copyright © 2014 TIS Inc. All rights reserved. 21
逆コンパイル
public final int sowa(int value) {	
return value != 0 ? value + sowa(value - 1) : value;	
}
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
ただの再帰
Copyright © 2014 TIS Inc. All rights reserved. 22
逆コンパイル
public final int sowa(int value, int acc) {	
do {	
if (value == 0)	
return acc;	
acc = value + acc;	
value = value - 1;	
} while (true);	
}
def	
  sowa(value:	
  Int,	
  acc:	
  Int	
  =	
  0):Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  acc	
  
	
  	
  else	
  sowa(value	
  -­‐	
  1,	
  value	
  +	
  acc)
最適
化!!
末尾再帰
Copyright © 2014 TIS Inc. All rights reserved. 23
tailrecアノテーション
末尾再帰になっていないメソッドの検出
import	
  scala.annotation.tailrec	
  
@tailrec	
  
final	
  def	
  sowa(value:	
  Int):	
  Int	
  =	
  
	
  	
  if	
  (value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
could	
  not	
  optimize	
  @tailrec	
  annotated	
  method	
  sowa:	
  
it	
  contains	
  a	
  recursive	
  call	
  not	
  in	
  tail	
  position
Copyright © 2014 TIS Inc. All rights reserved. 24
上書き禁止
@tailrec	
  
def	
  sowa(value:	
  Int,	
  acc:	
  Int	
  =	
  0):	
  Int	
  =	
  
	
  	
  if	
  (value	
  ==	
  0)	
  acc	
  
	
  	
  else	
  sowa(value	
  -­‐	
  1,	
  value	
  +	
  acc)
	
  @tailrec	
  
	
  	
  final	
  def	
  sowa(value:	
  Int,…
could	
  not	
  optimize	
  @tailrec	
  annotated	
  method	
  
sowa:	
  it	
  is	
  neither	
  private	
  nor	
  final	
  so	
  can	
  be	
  
overridden
overrideしてメソッドを変更出来ないようにfinalにする
privateにするか	
  
ローカルメソッドにしてもOK
Copyright © 2014 TIS Inc. All rights reserved. 25
総和
def	
  sowa(value:	
  Int)	
  :Int	
  =	
  
	
  	
  if(value	
  ==	
  0)	
  value	
  
	
  	
  else	
  value	
  +	
  sowa(value	
  -­‐	
  1)
ノーアキュム
@tailrec	
final	
  def	
  sowa(value:	
  Int,	
  acc:	
  Int	
  =	
  0):	
  Int	
  =	
  
	
  	
  if	
  (value	
  ==	
  0)	
  acc	
  
	
  	
  else	
  sowa(value	
  -­‐	
  1,	
  value	
  +	
  acc)
アキュム
Copyright © 2014 TIS Inc. All rights reserved. 26
!(階乗)
def	
  factorial(value:	
  Int):	
  Int	
  =	
  
	
  	
  if	
  (value	
  ==	
  0)	
  1	
  
	
  	
  else	
  value	
  *	
  factorial(value	
  -­‐	
  1)
@tailrec	
final def factorial(value: Int, acc: Long): Long =	
if (value == 0) acc	
else factorial(value - 1, value * acc)
ノーアキュム
アキュム
Copyright © 2014 TIS Inc. All rights reserved. 27
(おまけ)関数(計算式)を引数に
def calculate[A](value: Int, acc: A, func: (Int, A) => A): A = {	
def calculate(value: Int, acc: A): A =	
if (value == 0) acc	
else calculate(value - 1, func(value, acc))	
calculate(value, acc)	
}	
!
def factorial(value: Int) =	
calculate(value, 1L, (v: Int, acc: Long) => v * acc)	
!
def sowa(value: Int) =	
calculate(value, 0, (v: Int, acc: Int) => v + acc)	
計算(たすか	
  かけるか)
型(Int	
  か	
  Longか)
階乗
総和
Copyright © 2014 TIS Inc. All rights reserved. 28
まとめ:脱Javaライクの第一歩
再帰で関数型プログラミング	
  
アキュムレータで末尾再帰	
  
@tailrecで末尾かチェック	
  
!
注)	
  
間接的な再帰は最適化されません。
Copyright © 2014 TIS Inc. All rights reserved. 29
よくわからなかった、という方は	
  
スライドの最初に再帰してくださ
い。(コップ本より)
まとめ:脱Javaライクの第一歩
THANK YOU

再帰で脱Javaライク

  • 1.
    Copyright © 2014TIS Inc. All rights reserved. 再帰で   脱Javaライク 2015.2.21 TIS株式会社   前出祐吾
  • 2.
    Copyright © 2014TIS Inc. All rights reserved. 2 自己紹介 TIS株式会社 戦略技術センター! 社内向けエンジニア基盤の整備
       ▶ Scalaの活用検証! 甲賀忍者! 東京デビュー(先月)! @yugolf
  • 3.
    Copyright © 2014TIS Inc. All rights reserved. 3 Scalaってどうなの? コード量が半分くらいに!   ! とはいえ、Scalaって難しいんじゃないの?   ! 大丈夫、Javaのライブラリ呼べるし、Javaライクにも書ける し。   (その場合、コード量は半分にならないけど。。)   ! ん!?
  • 4.
    Copyright © 2014TIS Inc. All rights reserved. 4 脱Javaライクしてファ ンクショナルなプロ グラマーになるんです。  
  • 5.
    Copyright © 2014TIS Inc. All rights reserved. 5 Javaライクってなんですの? 例:総和 1からnまでを全部足す。   ! (めいっ   ぱい、   Javaっぽ   く足す)   def  sowa(value:  Int):  Int  ={      var  result  =  0;      for(n  <-­‐  1  to  value)  {          result  =  result  +  n;      }      return  result;   }         ! sowa(10)   //>  res4:  Int  =  55
  • 6.
    Copyright © 2014TIS Inc. All rights reserved. 6 脱Javaライク varはやめよう。   forループもやめよう。   再帰にしよう。 ①終了条件 ②自分呼出 ③計算
  • 7.
    Copyright © 2014TIS Inc. All rights reserved. 7 再帰にしよう:ステップ① def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1) ①終了条件 ②自分呼出 ③計算 計算対象の数値が0に なったとき
  • 8.
    Copyright © 2014TIS Inc. All rights reserved. 8 再帰にしよう:ステップ② def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1) ①終了条件 ②自分呼出 ③計算 自分()
  • 9.
    Copyright © 2014TIS Inc. All rights reserved. 9 再帰にしよう:ステップ③ def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1) ①終了条件 ②自分呼出 ③計算 対象値 + 自分()
  • 10.
    Copyright © 2014TIS Inc. All rights reserved. 10 再帰にしよう:実行1 def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1) ①終了条件 ②自分呼出 ③計算 sowa(10)                           //>  res6:  Int  =  55  
  • 11.
    Copyright © 2014TIS Inc. All rights reserved. 11 再帰にしよう:実行2 def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1) sowa(100000)   //>  java.lang.StackOverflowError   //|     at  Demo$$anonfun$main$1.sowa$1(Demo.scala:64)   //|     at  Demo$$anonfun$main$1.sowa$1(Demo.scala:64)   //|     at  Demo$$anonfun$main$1.sowa$1(Demo.scala:64) ①終了条件 ②自分呼出 ③計算
  • 12.
    Copyright © 2014TIS Inc. All rights reserved. 12 スタックオーバーフロー!? スタックのサイズを大きくする? (-Xss) !  スタックの消費量を減らそう!
  • 13.
    Copyright © 2014TIS Inc. All rights reserved. 13 スタックオーバーフロー対策 末尾再帰で最適化 ! ! ! ! 自分を呼び出したあとに計算(足し算)するのではなく、 計算したものを引数として、その結果を蓄積する。 ! 戻ってきたあとに計算がなく、呼出し元に戻るだけ。 def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1)
  • 14.
    Copyright © 2014TIS Inc. All rights reserved. 14 末尾再帰 アキュムレータ (accumulator) 自分を呼び出したあとに計算(足し算)するのではなく、 計算したものを引数として、その結果を蓄積する。 +  3  = +  2  = +  1  =
  • 15.
    Copyright © 2014TIS Inc. All rights reserved. 15 末尾再帰 アキュムレータ (accumulator) 自分を呼び出したあとに計算(足し算)するのではなく、 計算したものを引数として、その結果を蓄積する。 ①終了条件 ②自分呼出 ③アキュムレータ      による計算
  • 16.
    Copyright © 2014TIS Inc. All rights reserved. 16 末尾再帰にしよう:ステップ① def sowa(value: Int, acc: Int = 0):Int = if(value == 0) acc else sowa(value - 1, value + acc) 計算対象の数値が0に なったとき ①終了条件 ②自分呼出 ③アキュムレータ      による計算
  • 17.
    Copyright © 2014TIS Inc. All rights reserved. 17 末尾再帰にしよう:ステップ② def sowa(value: Int, acc: Int = 0):Int = if(value == 0) acc else sowa(value - 1, value + acc) 自分() ①終了条件 ②自分呼出 ③アキュムレータ      による計算
  • 18.
    Copyright © 2014TIS Inc. All rights reserved. 18 末尾再帰にしよう:ステップ③ def sowa(value: Int, acc: Int = 0):Int = if(value == 0) acc else sowa(value - 1, value + acc) 対象値 + アキュム         レータ ①終了条件 ②自分呼出 ③アキュムレータ      による計算
  • 19.
    Copyright © 2014TIS Inc. All rights reserved. 19 末尾再帰にしよう:実行 def sowa(value: Int, acc: Int = 0):Int = if(value == 0) acc else sowa(value - 1, value + acc) sowa(100000)   //>  res4:  Long  =  705082704 ①終了条件 ②自分呼出 ③アキュムレータ      による計算
  • 20.
    Copyright © 2014TIS Inc. All rights reserved. 20 逆アセンブル public  final  int  sowa(int);    Code:          0:  iload_1          1:  iconst_0          2:  if_icmpne          9          5:  iload_1          6:  goto                    18          9:  iload_1        10:  aload_0        11:  iload_1        12:  iconst_1        13:  isub        14:  invokevirtual  #101                //  Method  sowa2:(I)I        17:  iadd        18:  ireturn public  final  int  sowa(int,  int);    Code:          0:  iload_1          1:  iconst_0          2:  if_icmpne          7          5:  iload_2          6:  ireturn          7:  iload_1          8:  iconst_1          9:  isub        10:  iload_1        11:  iload_2        12:  iadd        13:  istore_2        14:  istore_1        15:  goto                    0 ただの再帰 末尾再帰
  • 21.
    Copyright © 2014TIS Inc. All rights reserved. 21 逆コンパイル public final int sowa(int value) { return value != 0 ? value + sowa(value - 1) : value; } def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1) ただの再帰
  • 22.
    Copyright © 2014TIS Inc. All rights reserved. 22 逆コンパイル public final int sowa(int value, int acc) { do { if (value == 0) return acc; acc = value + acc; value = value - 1; } while (true); } def  sowa(value:  Int,  acc:  Int  =  0):Int  =      if(value  ==  0)  acc      else  sowa(value  -­‐  1,  value  +  acc) 最適 化!! 末尾再帰
  • 23.
    Copyright © 2014TIS Inc. All rights reserved. 23 tailrecアノテーション 末尾再帰になっていないメソッドの検出 import  scala.annotation.tailrec   @tailrec   final  def  sowa(value:  Int):  Int  =      if  (value  ==  0)  value      else  value  +  sowa(value  -­‐  1) could  not  optimize  @tailrec  annotated  method  sowa:   it  contains  a  recursive  call  not  in  tail  position
  • 24.
    Copyright © 2014TIS Inc. All rights reserved. 24 上書き禁止 @tailrec   def  sowa(value:  Int,  acc:  Int  =  0):  Int  =      if  (value  ==  0)  acc      else  sowa(value  -­‐  1,  value  +  acc)  @tailrec      final  def  sowa(value:  Int,… could  not  optimize  @tailrec  annotated  method   sowa:  it  is  neither  private  nor  final  so  can  be   overridden overrideしてメソッドを変更出来ないようにfinalにする privateにするか   ローカルメソッドにしてもOK
  • 25.
    Copyright © 2014TIS Inc. All rights reserved. 25 総和 def  sowa(value:  Int)  :Int  =      if(value  ==  0)  value      else  value  +  sowa(value  -­‐  1) ノーアキュム @tailrec final  def  sowa(value:  Int,  acc:  Int  =  0):  Int  =      if  (value  ==  0)  acc      else  sowa(value  -­‐  1,  value  +  acc) アキュム
  • 26.
    Copyright © 2014TIS Inc. All rights reserved. 26 !(階乗) def  factorial(value:  Int):  Int  =      if  (value  ==  0)  1      else  value  *  factorial(value  -­‐  1) @tailrec final def factorial(value: Int, acc: Long): Long = if (value == 0) acc else factorial(value - 1, value * acc) ノーアキュム アキュム
  • 27.
    Copyright © 2014TIS Inc. All rights reserved. 27 (おまけ)関数(計算式)を引数に def calculate[A](value: Int, acc: A, func: (Int, A) => A): A = { def calculate(value: Int, acc: A): A = if (value == 0) acc else calculate(value - 1, func(value, acc)) calculate(value, acc) } ! def factorial(value: Int) = calculate(value, 1L, (v: Int, acc: Long) => v * acc) ! def sowa(value: Int) = calculate(value, 0, (v: Int, acc: Int) => v + acc) 計算(たすか  かけるか) 型(Int  か  Longか) 階乗 総和
  • 28.
    Copyright © 2014TIS Inc. All rights reserved. 28 まとめ:脱Javaライクの第一歩 再帰で関数型プログラミング   アキュムレータで末尾再帰   @tailrecで末尾かチェック   ! 注)   間接的な再帰は最適化されません。
  • 29.
    Copyright © 2014TIS Inc. All rights reserved. 29 よくわからなかった、という方は   スライドの最初に再帰してくださ い。(コップ本より) まとめ:脱Javaライクの第一歩
  • 30.