Successfully reported this slideshow.
Your SlideShare is downloading. ×

Listの実装から学ぶScala & 関数型プログラミング入門

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad

Check these out next

1 of 128 Ad
Advertisement

More Related Content

More from dcubeio (20)

Recently uploaded (20)

Advertisement

Listの実装から学ぶScala & 関数型プログラミング入門

  1. 1. Listの実装から学ぶ Scala & 関数型プログラミング入門 1
  2. 2. 自己紹介 羽田有輝同志社大学経済学部bizreach 新卒2年目学生時代はJavaを書いていたScalaは配属後初めて触った 2
  3. 3. アジェンダ 3
  4. 4. やることListの実装の前準備Scalaの特徴関数型プログラミングの特徴ScalaのList(collectionクラス)の特徴 Listの実装+ その際に使用するScalaの文法の説明 4
  5. 5. やらないこと環境構築の詳しい説明リストの実装で作成では触れないScalaの知識関数型の発展的内容(モナドの話とか) 5
  6. 6. Listの実装の前準備Scalaの特徴関数型プログラミングの特徴ScalaのList(collectionクラス)特徴 6
  7. 7. Scalaの特徴 JVMの環境で動くプログラミング言語である 全てがオブジェクトとして扱われるオブジェクト指向言語 上記に加え、関数型プログラミングの思想も取り入れているJavaよりも簡潔な表記が可能関数型、手続き型どちらの書き方も可能 7
  8. 8. Scala Official Page https://www.scala-lang.org/ 8
  9. 9. Javaよりも簡潔な表記が可能 9
  10. 10. このJavaのコードが class MyClass { private int index; private String name; public MyClass(int index, String name) { this.index = index; this.name = name; } } 10
  11. 11. Scalaだとこんな感じ ↓ Scalaだとこんなにスッキリかける ボイラープレートが減っていい感じ class MyClass (i: Int, n: String){ var index = i var name = n } class MyClass (var index: Int, var name: String) 11
  12. 12. Java 7 Java 8 //名前 大文字 含 判定 boolean nameHasUpperCase = false; for (int i = 0; i < name.length(); ++i) { if (character.isUpperCase(name.charAt(i))){ nameHasUpperCase=true; break; } } boolean nameHasUpperCase = name.chars().anyMatch( (intch) -> Character.isUpperCase((char)ch); ) 12
  13. 13. Scala ↓ ↓ val nameHasUpperCase: Boolean = name.exists{ (char: Char) => char.isUpper } // 型推論 型宣言省略可 val nameHasUpperCase: Boolean = name.exists(char => char.isUpper) // 関数内 一度 出 引数 _ 省略化 val nameHasUpperCase = name.exists(_.isUpper) 13
  14. 14. Scalaでは、 簡潔にコードを書けるように様々な工夫がなされている型推論によって、冗長な表記を省略する事ができる記述量を少なくする事により、本質的な部分に集中できる 14
  15. 15. 静的型付けのメリット+ LL言語のよう な書きやすさ 15
  16. 16. Listの実装の前準備Scalaの特徴関数型プログラミングの特徴ScalaのList(collectionクラス)特徴 16
  17. 17. 関数型の特徴 関数を引数にとったり、変数に入れる事ができる(first class object) 一度入れたものは変更不可能(immutable) 副作用がない(参照透過性) 状態を持たない 17
  18. 18. 参照透過性とは 同じ条件(引数)を与えれば必ず同じ結果(返り値)が得られる他のいかなる機能の結果にも影響を与えない関数を値に置き換えても、文脈が変わらない 18
  19. 19. 手続き型でも参照透過性は再現する事は可能だが考える事が多いimmutableやfirst-class-objectは参照透過性をシンプルに表現できるScalaでは変数や後述するListを基本的にimmutableとして扱う 19
  20. 20. Scalaではどのように表現され るのか 20
  21. 21. 関数を引数に取れる 変数に代入できる val nameHasUpperCase: Boolean = name.exists{char => char.isUpper } // char => char.isUpper 引数 Char 取 Boolean 返 関数 val f = (x: Int) => x * 2 // 変数 入 事 ( 場合 引数 型 省略 ) 21
  22. 22. 一度入れたものは変更不可能 scala> val x = 2 x: Int = 2 scala> x = x + 1 <console>:12: error: reassignment to val x = x + 1 ^ scala> val y = x + 1 // 新 値 返 y: Int = 3 scala> x // 元 変数 変更 res1: Int = 2 22
  23. 23. immutableの例 JavaのStringはimmutableである 副作用がある例 String hoge = "abcde" String reverse = hoge.toUpperCase() System.out.println(hoge) //abcde List<Int> list = new ArrayList<Int>(1,2,3,4,5) ShuffleList.suffle(list) System.out.println(list) //25341 23
  24. 24. Scalaでは JavaのStringのように基本的に新しく変更されたものを返すmutableにする事もできる 24
  25. 25. Listの実装の前準備Scalaの特徴関数型プログラミングの特徴ScalaのList(collectionクラス)特徴 25
  26. 26. Javaのリストの特徴 Javaは基本配列型のリストを使う(ArrayList) 要素を追加すると、参照先の値が変わる要素は後ろに追加される手続き型の言語はこのパターンが多い 26
  27. 27. JavaのList scala> val javaList: java.util.List[Int] = new java.util.ArrayList[Int]() javaList: java.util.List[Int] = [] scala> javaList res1: java.util.List[Int] = [] scala> javaList.add(1) res2: Boolean = true scala> javaList // java 変更可 res3: java.util.List[Int] = [1] scala> javaList.add(2) res4: Boolean = true 27
  28. 28. Scalaのリストの特徴 一方Scalaは基本単方向線形リスト追加する時に、新たなリストを返し、元データは変化しない要素は前に追加されるjavaにはない便利なメソッドがある関数型の言語はこのパターンが多い 28
  29. 29. ScalaのList scala> val scalaList = Nil // Nil 空 表 scalaList: scala.collection.immutable.Nil.type = List() scala> val intList = 1 :: Nil //:: 追加 intList: List[Int] = List(1) scala> scalaList //元 変数 変化 res1: scala.collection.immutable.Nil.type = List() scala> val intList2 = 2 :: intList // 要素 前 追加 intList2: List[Int] = List(2, 1) 29
  30. 30. JavaのListにはない便利なメソッド 2の倍数を2倍したリストを作成する時 // java List<Int> list = new ArrayList<Int>(1,2,3,4); List<Int> doubleList = new ArrayList<Int>(); for(Int num: list) { if(num % 2 == 0){ doubleList.add(num * 2) } } // scala val doubleList = List(1,2,3,4,5).filter{x => x % 2 == 0 }.map(x => x * 2) 30
  31. 31. Listのメソッドについて リストには関数を渡すメソッドが多くあるforループを使う必要がなくなるので、記述が短くなる新しいリストが返されるので、パイプ処理のようにメソッドを繋いでいける 31
  32. 32. collectionメソッドを使いこなせれば 非常に強力 32
  33. 33. 初心者の自分が感じた疑問 確かにScalaは書きやすい工夫をしてくれているが、理解するのが難しい関数を引数に取るってどういう事?なにがいいのか? immutableなのは便利なのはわかったけど、実際にコードでどうやって表現するの? headとtailに別れててなにが嬉しいのか普通に後ろに足したほうが便利なのではcollectionメソッドに慣れない 33
  34. 34. 自分がオススメする勉強法 タイトルにあるListの実装をしてみる事scala.collection.immutable.Listと同じ挙動をする物を作成する事社内のScala勉強会の題材として使われた 34
  35. 35. なぜオススメなのか 作成過程で、scalaの文法知識が多く出てくる関数型の特徴である、イミュータブルをどのように表現されているかイメージが付くListのデータ構造がわかるcollectionクラスの持つメソッドの理解が深まる 35
  36. 36. さっそく作ってみよう 36
  37. 37. ここからの流れ 実際にリストの実装を手順をこれから紹介しますそこで出て来るScalaの知識の説明をします実際に皆様にもコードを書いて頂いて、どう動くのかを見ていただきます 37
  38. 38. 実行環境 実装例 https://scalafiddle.io/ https://scalafiddle.io/sf/zh5tvwu/7 38
  39. 39. Scalaを一回も触った事がない人には厳しいかもし れないですが、 なんとなく、List実装がScala学ぶのに効果的だとい う事がわかって いただければ大丈夫です 39
  40. 40. List実装のルールの流れ 1. scala.collection.immutable.Listのデータ構造の再現 2. scala.collection.immutable.Listのメソッドの振る舞いを再現 40
  41. 41. Listとデータ構造の実装 41
  42. 42. Listとなるclassを作成 sealed abstract class MyList[+A] case object MyNil extends MyList[Nothing] case class MyCons[B]( head: B, tail: MyList[B] ) extends MyList[B] 42
  43. 43. sealedとは? 同一ソースファイル内のクラスは継承できるが、別ファイルのクラスからは継承できなくなるこれを使う事で、同一ファイルに存在するサブクラス以外存在しない事を保証できるJavaでいうEnum(列挙型)を表現できる 43
  44. 44. MyList MyNil(要素が一つもない) MyCons(要素が一つ以上) の2種類で表現できる 44
  45. 45. case classとは? 拡張されたclass 便利な機能やメソッドがある(出てきた時に紹介) 直積型を表現できる(javaでいうjava beans? 構造体?) 45
  46. 46. MyCons head(一番最初の要素) tail(それ以外の要素) の2種類で表現できる 46
  47. 47. たった三行で直和型(列挙型× 直積型)を表現できる ※ 代数的データ型についてはこちらの記事が詳し く書いてある http://qiita.com/shigemk2/items/31f6bdbf4dfebb0b9ad 47
  48. 48. Listはこのように表現される [] = MyNil [1] = MyCons(1,MyNil) [1,2] = MyCons(1,MyCons(2,MyNil)) [1,2,3] = MyCons(1,MyCons(2,MyCons(3,MyNil))) 48
  49. 49. objectとは? Javaでいうsingletonクラスインスタンスが一つだけ生成される // MyNil 何 状態 表現可能 為、 一 十分 case object MyNil extends MyList[Nothing] 49
  50. 50. 型パラメータについて JavaでいうGenericsの事AやBなど一文字の英数字で表現される[A] インスタンスを生成する際に、型を指定する事ができる 50
  51. 51. 型の継承関係 http://docs.scala-lang.org/tutorials/tour/unified- types.html 51
  52. 52. NothingとはNothingとはすべてのクラスの基底クラス(サブクラス) Nothing -> Int, Nothing -> String など 52
  53. 53. 非変と共変 非変MyList[A] MyList[String] のList[Any] のサブクラスではない 共変MyList[+A] MyList[String] -> List[Any] のサブクラスである 53
  54. 54. 非変のリストだと scala> sealed abstract class MyList[+A] defined class MyList scala> case object MyNil extends MyList[Nothing] defined object MyNil scala> case class MyCons[B]( hd: B, tl: MyList[B]) extends MyList[B] defined class MyCons scala> val list: MyList[Int] = MyNil //空 表現 <console>:13: error: type mismatch; found : MyNil.type required: MyList[Int] 54
  55. 55. この三行だけでも色々学べる sealed abstract class MyList[+A] case object MyNil extends MyList[Nothing] case class MyCons[B]( head: B, tail: MyList[B] ) extends MyList[B] 55
  56. 56. headとtail,isEmptyを持たせる sealed abstract class MyList[+A] { val head: A val tail: MyList[A] def isEmpty: Boolean } case object MyNil extends MyList[Nothing] { val head: Nothing = throw new NoSuchElementException("head of empty MyList") val tail: MyList[Nothing] = throw new NoSuchElementException("tail of empty MyList") def isEmpty = true } case class MyCons(head: A tail: MyList[A]) extends MyList[A] { def isEmpty = false 56
  57. 57. case classの機能 case class MyCons(head: A tail: MyList[A]) extends MyList[A] { // case class 場合、引数 物 宣言 def isEmpty = false } 57
  58. 58. MyNilについて 本家のNilを真似て、headとtailが参照されようとすると、例外を投げるようにした case object MyNil extends MyList[Nothing] { val head: Nothing = throw new NoSuchElementException("head of empty MyList") val tail: MyList[Nothing] = throw new NoSuchElementException("tail of empty MyList") def isEmpty = true } 58
  59. 59. MyListのインスタンスを作成 sealed abstract class MyList[+A] {...} case object MyNil extends MyList[Nothing] {...} case class MyCons[B]( head: B, tail: MyList[B] ) extends MyList[B] {...} object MyList { def apply[A](x: A*): MyList[A] = { if (x.isEmpty) MyNil else MyCons(x.head, apply(x.tail: _*)) } } 59
  60. 60. classと同名のObject コンパニオンオブジェクトと呼ぶjavaでいうクラスメソッド(static method)はこちらに書くインスタンスメソッドはclassの方に書く 60
  61. 61. applyメソッドとは 主にインスタンスを生成する目的で使用する特別なメソッドMyList.apply() は、MyList() のようにメソッドを省略して呼べるつまりnewせずにインスタンスが呼び出せる 61
  62. 62. case classの機能その2 case class は暗黙的にコンパニオンオブジェクトが生成されるその際、applyも暗黙的に定義される def apply[A](x: A*): MyList[A] = { if (x.isEmpty) MyNil // MyCons case class 為、 // 用意 else MyCons(x.head, apply(x.tail: _*)) } // 暗黙的 定義 // object MyCons { // def apply[A](head:A,tail:MyList[A]) = new MyCons(head, tail) //} 62
  63. 63. A*とは 可変長引数と呼ぶAの型のオブジェクトをコンマ区切りで要素をsequenceとして入れる事ができるMyList(1,2,3,4,5)とする事で引数にSeq(1,2,3,4,5)を渡す事ができる //1,2,3,4,5 区切 入 x Seq[A] 変換 def apply[A](x: A*): MyList[A] = { // x = Seq(1,2,3,4,5) if (x.isEmpty) MyNil // seq 引数 時 _* 使用 else MyCons(x.head, apply(x.tail: _*)) } 63
  64. 64. 要素を追加できるようにする sealed abstract class MyList[+A] { val head: A val tail: MyList[A] def isEmpty: Boolean // 要素追加 持 def ::[B >: A](x: B): MyList[B] = ??? } 64
  65. 65. :: (要素追加) 65
  66. 66. 要素を追加するメソッドを作成 def ::[B >: A](x: B): MyList[B] = MyCons(x,this) 66
  67. 67. >: (下限境界)について 本来Any型からNothing型まですべての下限境界で指定したクラス自身とその親クラスだけ受け付ける下限より下のSubクラスは境界のクラスに変換される 67
  68. 68. 例 Man -> Human -> Creature scala> class Creature defined class Creature scala> class Human extends Creature defined class Human scala> class Man extends Human defined class Man 68
  69. 69. scala> val list = MyList[Human]() //型 直接指定 事 list: MyList[Human] = MyNil scala> val CreatureList = new Creature :: list CreatureList: MyList[Creature] = MyCons(Creature@5e9b30c4,MyNil) scala> val HumanList = new Human :: list HumanList: MyList[Human] = MyCons(Human@27d684d6,MyNil) // Human型 下 下限 Human型扱 scala> val ManList = new Man :: list ManList: MyList[Human] = MyCons(Man@7670cb40MyNil) 69
  70. 70. メソッド名が:で終わる物について scala> val a = List(1,2,3,4) a: List[Int] = List(1, 2, 3, 4) scala> a.::(5) res0: List[Int] = List(5, 1, 2, 3, 4) scala> a res1: List[Int] = List(1, 2, 3, 4) scala> 0 :: a // 呼 出 res2: List[Int] = List(0, 1, 2, 3, 4) 70
  71. 71. Listのデータ構造の完成 scala> val list = List(1,2,3,4,5) // 作成 際 、new list: MyList[Int] = MyList(1, MyList(2, MyList(3, MyList(4, MyList scala> list.head // 一番最初 要素 取得 res1: Int = 1 scala> list.tail // 以外 取得 res2: MyList[Int] = MyList(2, MyList(3, MyList(4, MyList(5, MyNil scala> list // 副作用 res3: MyList[Int] = MyList(1, MyList(2, MyList(3, MyList(4, MyList scala> val newList = 0 :: list // 追加 res4: MyList[Int] = MyList(0,MyList(1, MyList(2, MyList(3, MyList 71
  72. 72. collectionメソッドの実装 72
  73. 73. Listのメソッドについて Listを始め、collectionクラスには様々なメソッドがあるその多くは他の関数型言語でも存在している関数型プログラミングで頻出のメソッドに絞って実装していく実際に振る舞いを真似てみる事で理解が深まる 73
  74. 74. インターフェースを定義 sealed abstract class MyList[+A] { ... def reverse: MyList[A] = ??? def map[B](f: A => B): MyList[B] = ??? def flatMap[B](f: A => MyList[B]): MyList[B] = ??? def filter[B](f: A => Boolean): MyList[B] = ??? def foreach[B](f: A => Unit): Unit = ??? def foldLeft[B](z: B)(f: (B, A) => B): B = ??? def foldRight[B](z: B)(f: (A, B) => B): B = ??? 74
  75. 75. reverse 75
  76. 76. 目標 scala> val a = List(1,2,3,4) //a: List[Int] = List(1, 2, 3, 4) scala> a.reverse //a: List[Int] = List(4, 3, 2, 1) 76
  77. 77. ダメな例(例えば) def reverse: A = { var currentList = this var newList = MyNil // var 使 mutable 方 while(currentList.isEmpty){ var newList = currentList.head :: newList var currentList = currentList.tail } newList } 77
  78. 78. Q. varを使わずにどうやって計 算すればいいのか -> どうやって計算の途中結果をもてばいいのか? 78
  79. 79. A. 再帰処理を使いましょう 79
  80. 80. 関数型的な書き方 ある関数の中で、その関数を呼び出すと再帰になる処理とは関係ない変数(result,tmpなど)を使わなくてもいい記述もすっきりする 80
  81. 81. def reverse: MyList[A] = { // target 短 acc 形成 // target 空MyNil 時 acc 返 def loop(target: MyList[A],acc: MyList[A] = MyNil) = { target match { case MyNil => acc case MyCons(head,tail) => loop(tail,head ::acc) } } loop(this) } 81
  82. 82. match とは パターンマッチと呼ばれ、javaのcase文のような物case classレベルでの分岐が容易にできるclassを構成する各要素を変数に束縛できる 82
  83. 83. クラスの要素分解について case MyCons(h,t) とする事で、hは最初の要素、tはそれ以外という変数に束縛されるここではMyConsのunapplyメソッド(抽出メソッド)の戻り値に依存するMyCons(h,t) == MyCons.unapply(this) 83
  84. 84. case classの機能その3 applyと同じくunapplyも暗黙的に定義されるここを変更する事で、パターンマッチの時に使える変数を変更できる object MyCons { def unapply(c: MyCons[A]):Option[(A,MyList[A])] = (c.head,c.tail) } 84
  85. 85. Listが線形リストである理由 データをイミュータブルなままで変換していくには、再帰処理が不可欠頭とそれ以外というデータの持ち方は、再帰処理で使い易い 85
  86. 86. map 86
  87. 87. mapとは Listの各要素に引数に指定した関数を各要素に適用した新しいListを作成する 87
  88. 88. 目標 scala> val a = List(1,2,3,4) //a: List[Int] = List(1, 2, 3, 4) scala> a.map(x => x * 2) res0: List[Int] = List(2, 4, 6, 8) scala> a.map(_.toString) // 型 変更化 res1: List[String] = List("1", "2", "3", "4") 88
  89. 89. // 例 def map[B](f:A => B): MyList[B] = { if(isEmpty) MyNil else f(head) :: tail.map(f) } 89
  90. 90. なぜ駄目のか 再帰を使っているが末尾再帰になっていない為関数が呼ばれ続ける為、リストの長さに比例してコールスタックが消費される長いリストになるとstackoverflowを起こす為、ループに変換する必要がある末尾再帰になっているとコンパイルが単なるループに変換してくれる(スタックセーフ) 90
  91. 91. mapメソッドを末尾再帰で作る def map[B](f:A => B): MyList[B] = { def loop(target: MyList[A],acc: MyList[B] = MyNil) = { target Match { case MyNil => acc // 自分 呼 出 終了 case MyCons(head,tail) => loop(tail,f(head) :: acc) } } loop(this) } 91
  92. 92. リストが逆になるのでreverseメソッドを使う。 def map[B](f:A => B): MyList[B] = { def loop(target: MyList[A],acc: MyList[B] = MyNil) = { target match { case MyNil => acc case MyCons(head,tail) => loop(tail,f(head) :: acc) } } loop(this).reverse } 92
  93. 93. デフォルト値の指定 引数にデフォルト値を設定できる引数に何も指定されていない場合はデフォルト値を使用する def loop(target: MyList[A],acc: MyList[B] = MyNil) = ... //第2引数 時、acc MyNil loop(this).reverse 93
  94. 94. filter 94
  95. 95. filterとは Booleanを返す関数(述語関数)でTrueの要素だけ残すList(1,2,3,4,5,6).filter(x => x % 2 == 0) = List(2,4,6) 95
  96. 96. 目標 scala> val list = List(1,2,3,4) //a: List[Int] = List(1, 2, 3, 4) scala> list.filter(x => x % 2 == 0) res0: List[Int] = List(2,4) 96
  97. 97. 実装はこんな感じ def filter(f:A => Boolean): MyList[B] = { def loop(target: MyList[A],acc: MyList[B] = MyNil) = { target match { case MyNil => acc case MyCons(head,tail) if f(head) => loop(tail,head :: acc) case _ => loop(tail,acc) } } loop(this).reverse } 97
  98. 98. パターンガード パターンマッチした値に対して、ifを定義して分岐させる事ができる束縛した変数も使用できる一致したcase class に対して、更に絞り込みをかけたいときに便利 98
  99. 99. flatMap 99
  100. 100. flatMapとは ネストしたListを一つのListにしてくれるList(List(1,2,3),List(4,5),List(6)) => List(1,2,3,4,5,6) flatten + map 100
  101. 101. 目標 scala> val list = List(1,2,3,4) //a: List[Int] = List(1, 2, 3, 4) scala> list.map(x => List(x,x)) res0: List[Int] = List(List(1,1),List(2,2,),List(3,3),List(4,4)) scala> list.flatMap(x => List(x,x)) res1: List[Int] = List(1,1,2,2,3,3,4,4) 101
  102. 102. 要素を結合するメソッドが必要 102
  103. 103. :::(List結合) 103
  104. 104. 要素を追加するメソッドを作成 def :::[B >: A](l: MyList[B]): MyList[B] = { def loop(a1: MyList[B], a2: MyList[B]) = { a1 match { case MyNil => a2 case MyCons(h,t) => MyCons(h, loop(t, a2)) } } loop(l,this) } 104
  105. 105. 実装はこんな感じ def flatMap(f:A => MyList[A]): MyList[B] = { def loop(target: MyList[A],acc: MyList[B] = MyNil)= { target match { case MyNil => acc case MyCons(head,tail) => loop(tail,f(head) ::: acc) } } loop(this).reverse } 105
  106. 106. foreach 106
  107. 107. foreachとは 返り値がない(副作用がある)物に対して用いるUnit(返す物はないという意味のもの)を返すvoidとは違う 107
  108. 108. 目標 scala> val a = List(1,2,3,4) //a: List[Int] = List(1, 2, 3, 4) scala> a.foreach(x => println(x)) 1 2 3 4 108
  109. 109. 実装はこんな感じ def foreach(f: A => Unit): Unit = { this match { case MyNil => () // Unit () 表 case MyCons(head, tail) => { f(head) t.foreach(f) } } } 109
  110. 110. foldLeft 110
  111. 111. foldLeftとは fold(畳み込み) + Left(一番前から) Listを前の要素から順に関数を適用するaccに蓄積されていく再帰の原理とほとんど同じ 111
  112. 112. 目標 scala> val list = List(1,2,3,4) // list: List[Int] = List(1, 2, 3, 4) // acc 関数 結果 蓄積 scala> list.foldLeft("0"){ (acc,x) => | acc + x.toString | } res0: String = "01234" 112
  113. 113. 実装はこんな感じ def foldLeft[B](z: B)(f: (B, A) => B): B = { def loop(l: List[A], acc: B)(f: (B, A) => B) = l match { case Nil => acc case Cons(h,t) => loop(t, f(acc,h))(f) } loop(this,z)(f) } 113
  114. 114. foldLeftでいままでのメソッドを書き 換え可能 def map[B](f: A => B): MyList[B] = foldLeft[MyList[B]](MyNil) { (acc, xs) => f(xs) :: acc }.reverse 114
  115. 115. foldRight 115
  116. 116. foldRight fold(畳み込み) + Left(一番後ろから) foldLeftとほぼ同じだが、accが第二引数の方になるListを後ろの要素から順に関数を適用する難易度高め 116
  117. 117. 目標 scala> val list = List(1,2,3,4) // list: List[Int] = List(1, 2, 3, 4) scala> list.foldRight("5"){ (x,acc) => | acc + x.toString | } res0: String = "54321" 117
  118. 118. 実装はこんな感じ def foldRight[B](z: B)(f: (A, B) => B): B = { def loop(xs: MyList[A], acc: B => B = identity) = { xs match { case MyNil => acc case MyCons(h,t) => loop(t,x => acc(f(h,x))) } } loop(this)(z) } 118
  119. 119. 関数は引数が決まるまで評価されない 内側から実行される-> 右から関数適用 List(1,2,3,4,5) 場合 List(1,2,3,4,5).foldRight(0)((x,acc) => x + acc) (1,(2,3,4,5)) => loop((2,3,4,5), x => f(1,x)) (2,(3,4,5) => loop((3,4,5), x => f(1,f(2,x))) (3,(4,5) => loop((4,5), x => f(1,f(2,f(3,f(4,f(5,x))))) 119
  120. 120. 最低限の機能のListが完成! scala> val a = MyList(1,2,3,4,5) a: MyList[Int] = MyCons(1,MyCons(2,MyCons(3,MyCons(4,MyCons(5,MyNil scala> a.map(x => x * 2) res1: MyList[Int] = MyCons(2,MyCons(4,MyCons(6,MyCons(8,MyCons(10 scala> a.filter(x => x % 2 == 0) res2: MyList[Int] = MyCons(2,MyCons(4,MyNil)) scala> a.flatMap(x => x :: x :: MyNil) res3: MyList[Int] = MyCons(1,MyCons(1,MyCons(2,MyCons(2,MyCons(3 scala> a.foreach(println) 1 2 3 120
  121. 121. ちなみに 実際のscala.collection.immutable.Listでは、末尾再帰を用いず、手続き型で書いている手続き型でやっている事が関数型のインターフェースでラッピングされている final override def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, . . val h = new ::[B](f(head), Nil) var t: ::[B] = h var rest = tail while (rest ne Nil) { val nx = new ::(f(rest.head), Nil) t.tl = nx t = nx rest = rest.tail } . . } 121
  122. 122. メソッドはたくさんある Scalaコレクションメソッド 122
  123. 123. 目指せ全実装! 123
  124. 124. まとめ Listの実装は、scalaにあってjavaにはない機能に沢山触れる事ができる更に、関数型の基礎的な部分を学習できるscalaをある程度理解してきたタイミングでこれをやると効果的 124
  125. 125. オススメの参考書 125
  126. 126. Scalaの作者が書いた入門書の訳本まずこちらをある程度やってからList実装をやると効果的 関数型プログラミングについて、Scalaで実装していきながら学べるこちらでもListの実装するエクサイズが最初の方で出て来る難易度高め スケーラブルプログラミング fp in scala 126
  127. 127. おまけhttps://scalafiddle.io/sf/lbqKNT6/11 127
  128. 128. ご静聴ありがとうございました 128

×