class: center, middle # The programmer that controls the `traverse`, controls the code. Scala Matsuri 2023 2023/04/15
@gakuzzzz
traverse を制する者はコードを制す
--- class: left, top ## Who am I * Manabu NAKAMURA * Twitter: [@gakuzzzz](https://twitter.com/gakuzzzz) * Tech to Value Co.,Ltd. CEO * Alp, Inc. Tech Lead
自己紹介: 中村 学 / Twitter: @gakuzzzz
株式会社Tech to Value 代表 / アルプ株式会社 テックリード
--- class: left, top ## Note The Scala code in this slide uses version 3.2.2. It uses [cats](https://typelevel.org/cats/) and assumes that the following imports have been written for all sample code. ```Scala import cats._ import cats.data._ import cats.syntax.all._ ```
このスライドにおける Scala コードは version 3.2.2 を利用しています。
また cats を利用しており、全てのサンプルコードには上記 import が行われている前提です。
--- class: left, top ## the Iterator pattern Loops are an important component of programs.
プログラムを構成する重要な要素としてループがあります。
--- class: left, top ## the Iterator pattern Loops are an important component of programs. Among them, loop processing to iterate over data structures such as collections is frequently used.
中でもコレクションなどのデータ構造に対し
その要素をイテレートするループ処理は頻出します。
--- class: left, top ## the Iterator pattern Loops are an important component of programs. Among them, loop processing to iterate over data structures such as collections is frequently used. It is patterned as the Iterator pattern in the GoF design patterns (Gamma et al., 1995).
GoFのデザインパターンにおいてもイテレータパターンとしてパターン化されています。
--- class: left, top ## the Iterator pattern Nowadays, many major languages have a special syntax for the Iterator pattern. ```typescript // JavaScript / TypeScript const list = [1, 2, 3, 4, 5]; for (const value of list) { console.log(value); } ```
多くのメジャーな言語でイテレータパターンを特別扱いした構文が用意されています。
--- class: left, top ## the Iterator pattern Nowadays, many major languages have a special syntax for the Iterator pattern. ```java17 // Java var list = List.of(1, 2, 3, 4, 5); for (var value : list) { System.out.println(value); } ```
多くのメジャーな言語でイテレータパターンを特別扱いした構文が用意されています。
--- class: left, top ## the Iterator pattern Nowadays, many major languages have a special syntax for the Iterator pattern. ```python # Python list = [1, 2, 3, 4, 5] for value in list: println(value) ```
多くのメジャーな言語でイテレータパターンを特別扱いした構文が用意されています。
--- class: left, top ## Higher order functions On the other hand, many major languages now support higher-order functions.
一方、現在のメジャーな言語の多くでは高階関数がサポートされるようになり、
--- class: left, top ## Higher order functions On the other hand, many major languages now support higher-order functions. For simple iterations, it is common to use higher-order functions such as `map` and `filter`.
単純なイテレーションに関しては map や filter といった
高階関数が使われる事も一般的になってきました。
--- class: left, top ## Higher order functions On the other hand, many major languages now support higher-order functions. For simple iterations, it is common to use higher-order functions such as `map` and `filter`. ```typescript // JavaScript / TypeScript const list = [1, 2, 3, 4, 5]; const result = list.map(i => i * 2); ```
単純なイテレーションに関しては map や filter といった
高階関数が使われる事も一般的になってきました。
--- class: left, top ## Higher order functions On the other hand, many major languages now support higher-order functions. For simple iterations, it is common to use higher-order functions such as `map` and `filter`. ```java17 // Java var list = List.of(1, 2, 3, 4, 5); var result = list.stream().map(x -> x * 2).toList(); ```
単純なイテレーションに関しては map や filter といった
高階関数が使われる事も一般的になってきました。
--- class: left, top ## Higher order functions On the other hand, many major languages now support higher-order functions. For simple iterations, it is common to use higher-order functions such as `map` and `filter`. ```python # Python list = [1, 2, 3, 4, 5] result = list(map(lambda i: i * 2, list)) ```
単純なイテレーションに関しては map や filter といった
高階関数が使われる事も一般的になってきました。
--- class: left, top ## Complex Iteration However, there are still many situations where the loop syntax is used for complex iterations.
しかしながら、複雑なイテレーションについては
まだループ構文が使われる状況が多い様です。
--- class: left, top ## Complex Iteration However, there are still many situations where the loop syntax is used for complex iterations. for example: Using `break`, Depending previous and next elements, Returning multiple results.
break が必要だったり、前後の要素に依存したり、
複数の値を返す必要があったりなど
--- class: left, top ## Complex Iteration Such loops are difficult to write tests, and are difficult to tune the performance by using parallel loops.
こういったループはそのままではテストが書きづらかったり
並列ループによる高速化が難しかったりします。
--- class: left, top ## the `traverse` In fact, there is a higher-order function that can express all of the same content about the loop process that can be written by the Iterator pattern.
実はこの the Iterator pattern によって書くことができるループ処理について、
同じ内容を全て表現する事ができる高階関数が存在しています。
--- class: left, top ## the `traverse` In fact, there is a higher-order function that can express all of the same content about the loop process that can be written by the Iterator pattern. That is the `traverse`.
それが traverse です。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` Now let's look at the definition of `traverse`.
まずは traverse の定義を見てみましょう。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` Now let's look at the definition of `traverse`. The `traverse` method can be defined for various data structures, here we define it as an extension method of `List` as an example.
traverse メソッドは 色々なデータ構造で定義できるのですが、
ここでは例として List の拡張メソッドとして定義しましょう。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` Now let's look at the definition of `traverse`. The `traverse` method can be defined for various data structures, here we define it as an extension method of `List` as an example. This is a little difficult, but let's look at them in order.
いきなりちょっと難しい定義が出てきましたが、順番に見ていきましょう。
--- class: left, top ## `traverse` of `List` ```scala extension [`A`](self: List[`A`]) with { def traverse[G[_]: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` First, `List` has a type parameter `A` that indicates the type of its own elements.
まず List は自身が持つ要素の型を示す A という型パラメータを持っています。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[`G`[_]: Applicative, `B`](f: (A) => G[B]): G[List[B]] } ``` And the `traverse` method introduces two type parameters, `G` and `B`.
そして traverse メソッドでは G と B という二つの型パラメータを導入しています。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[`G[_]`: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` `G` is `G[_]`, which also represents a type that takes one type parameter.
G は G[_] となっており、これも型パラメータを一つとる型を表します。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[`G[_]`: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` `G` is `G[_]`, which also represents a type that takes one type parameter. For example, we can assign `List`, `Option`, or `Either[String, *]` to `G[_]`.
例えば List とか Option とか Either[String, *] とかを G[_] に指定することができます。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]`: Applicative`, B](f: (A) => G[B]): G[List[B]] } ``` In addition, `G[_]` has a constraint, requiring a typeclass of `Applicative`.
さらに G[_] には制約がついており、Applicative の型クラスを要求しています。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]`: Applicative`, B](f: (A) => G[B]): G[List[B]] } ``` In addition, `G[_]` has a constraint, requiring a typeclass of `Applicative`. We will talk about what is `Applicative` later.
Applicative については後ほどまた触れますので、
ここではそういうものがあるんだーくらいに思って頂ければ。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: `(A) => G[B]`): G[List[B]] } ``` It then takes as its argument "a function that takes an element type `A` and returns a `B` wrapped in a `G`".
「要素型の A を受け取り G に包まれた B を返す関数」を引数として受け取ります。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: `(A) => G[B]`): G[List[B]] } ``` It then takes as its argument "a function that takes an element type `A` and returns a `B` wrapped in a `G`". For example, if the instance is of type `List[String]`, then `A` becomes `String` and functions of type `String => Option[Int]` are accepted as arguments.
例えば List[String] 型のインスタンスだったとしたら、
A が String になり、String => Option[Int] 型の関数などが引数として渡せます。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: (A) => G[B]): `G[List[B]]` } ``` The result is `List[B]` wrapped in `G`.
結果として G に包まれた List[B] が返されます。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` Let's look at an example of actual use.
実際に利用例を見てみましょう。
--- class: left, top ## `traverse` of `List` ```scala extension [A](self: List[A]) with { def traverse[G[_]: Applicative, B](f: (A) => G[B]): G[List[B]] } ``` Let's look at an example of actual use. The above definition is just a hypothetical example, Using cats we can be used as an instance method of `List` as if it were defined as such.
上記の定義はあくまで仮定の例ですが、
cats を使うとあたかもこのような定義がされてるかのように
List のインスタンスメソッドとして使う事ができます。
--- class: left, top ## `traverse` of `List` If `Some` is returned for all elements, ```scala val list: List[String] = List("1", "2", "3", "4", "5") val result: Option[List[Int]] = list.traverse(_.toIntOption) println(result) ```
全ての要素で Some を返した場合、
--- class: left, top ## `traverse` of `List` If `Some` is returned for all elements, ```scala val list: List[String] = List("1", "2", "3", "4", "5") val result: Option[List[Int]] = list.traverse(_.toIntOption) println(result) ``` the result is a `List` wrapped in `Some`. ```scala Some(List(1, 2, 3, 4, 5)) ```
結果は Some で包まれた List が返されます。
--- class: left, top ## `traverse` of `List` If there is an element that is `None` on the way, ```scala val list: List[String] = List("1", "2", `"aaa"`, "4", "5") val result: Option[List[Int]] = list.traverse(_.toIntOption) println(result) ```
途中 None になる要素が存在した場合、
--- class: left, top ## `traverse` of `List` If there is an element that is `None` on the way, ```scala val list: List[String] = List("1", "2", `"aaa"`, "4", "5") val result: Option[List[Int]] = list.traverse(_.toIntOption) println(result) ``` the result is `None`. ```scala None ```
結果は None が返されます。
--- class: left, top ## the `traverse` ```scala val list: List[String] = List("1", "2", "3", "4", "5") val result: Option[List[Int]] = list.traverse(_.toIntOption) println(result) ``` ```scala Some(List(1, 2, 3, 4, 5)) ``` In short, a `traverse` method is an operation that takes a function that returns an element as some `Applicative` value and keeps the original structure as an `Applicative` value.
要するに traverse メソッドは、「要素を何かしらの Applicative な値にして返す関数」
を引数にとり Applicative な値として元の構造を保つ操作、と言えます。
--- class: left, top ## `Applicative` It is difficult to understand what is meant by "a function that returns an element as some `Applicative` value" and keeps the original structure as an `Applicative` value," isn't it?
「要素を何かしらの Applicative な値にして返す関数」を引数にとり
Applicative な値として元の構造を保つ操作、と言われても何が何やらですよね?
--- class: left, top ## `Applicative` It is difficult to understand what is meant by "a function that returns an element as some `Applicative` value" and keeps the original structure as an `Applicative` value," isn't it? Many of you may be wondering what an `Applicative` value is.
そもそも Applicative な値とはなんぞや?となってる方も多いかと思います。
--- class: left, top ## `Applicative` It is difficult to explain what `Applicative` is in this session, because it is too abstract a concept.
あまりにも抽象的な概念のため
Applicative とは何か、をこのセッション内で判りやすく伝える事は難しいです。
--- class: left, top ## `Applicative` It is difficult to explain what `Applicative` is in this session, because it is too abstract a concept. Here, it is sufficient to understand that Applicative is a typeclass with the following two methods.
ここでは以下の二つのメソッドを持った型クラスとだけ認識していただければ大丈夫です。
--- class: left, top ## `Applicative` It is difficult to explain what `Applicative` is in this session, because it is too abstract a concept. Here, it is sufficient to understand that Applicative is a typeclass with the following two methods. - `pure`: It generates a value of type `G[A]` from a value of type `A`
pure: A 型の値から G[A] 型の値を生成するメソッド
--- class: left, top ## `Applicative` It is difficult to explain what `Applicative` is in this session, because it is too abstract a concept. Here, it is sufficient to understand that Applicative is a typeclass with the following two methods. - `pure`: It generates a value of type `G[A]` from a value of type `A` - `map2`: It generates a value `G[C]` from two `G[_]` wrapped values `G[A]` and `G[B]`
map2: G[_] に包まれた二つの値 G[A] と G[B] から、G[C] という値を生成できるメソッド
--- class: left, top ## `Applicative` - `pure`: It generates a value of type `G[A]` from a value of type `A` - `map2`: It generates a value `G[C]` from two `G[_]` wrapped values `G[A]` and `G[B]` In code, In code, It is the type as follows. ```scala trait Applicative[G[_]] { def pure[A](a: A): G[A] def map2[A, B, C](a: G[A], b: G[B])(f: (A, B) => C): G[C] } ```
コードで表現するとこのようなインターフェイスが実装できる型となります。
--- class: left, top ## `Applicative` ```scala trait Applicative[G[_]] { def pure[A](a: A): G[A] def map2[A, B, C](a: G[A], b: G[B])(f: (A, B) => C): G[C] } ``` `pure` is an operation that wraps a value.
pure は値を包む操作ですね。
--- class: left, top ## `Applicative` ```scala trait Applicative[G[_]] { def pure[A](a: A): G[A] def map2[A, B, C](a: G[A], b: G[B])(f: (A, B) => C): G[C] } ``` `pure` is an operation that wraps a value. For `Option` it is `Option.apply` and ... ```scala given Applicative[Option] with { def pure[A](a: A): Option[A] = Option(a) ... ```
Option であれば Option.apply ですし、
--- class: left, top ## `Applicative` ```scala trait Applicative[G[_]] { def pure[A](a: A): G[A] def map2[A, B, C](a: G[A], b: G[B])(f: (A, B) => C): G[C] } ``` `pure` is an operation that wraps a value. For `Option` it is `Option.apply` and for `List` it is `List.apply`. ```scala given Applicative[Option] with { def pure[A](a: A): Option[A] = Option(a) ... ``` ```scala given Applicative[List] with { def pure[A](a: A): List[A] = List(a) ... ```
Option であれば Option.apply ですし、List であれば List.apply です
--- class: left, top ## `Applicative` ```scala trait Applicative[G[_]] { def pure[A](a: A): G[A] def map2[A, B, C](a: G[A], b: G[B])(f: (A, B) => C): G[C] } ``` `map2` is the two values version of `map`.
では map2 とはどういう操作かというと map の二つ版の事です。
--- class: left, top ## `Applicative` ```scala trait Applicative[G[_]] { def pure[A](a: A): G[A] def map2[A, B, C](a: G[A], b: G[B])(f: (A, B) => C): G[C] } ``` `map2` is the two values version of `map`. For `Option` as following and ```scala given Applicative[Option] with { ... def map2[A, B, C](a: Option[A], b: Option[B]) (f: (A, B) => C): Option[C] = { (a, b) match { case (Some(a), Some(b)) => Some(f(a, b)) case _ => None } } ```
Option であれば上記のようになりますし、
--- class: left, top ## `Applicative` ```scala trait Applicative[G[_]] { def pure[A](a: A): G[A] def map2[A, B, C](a: G[A], b: G[B])(f: (A, B) => C): G[C] } ``` `map2` is the two values version of `map`. For `List` as following ```scala given Applicative[List] with { ... def map2[A, B, C](a: List[A], b: List[B]) (f: (A, B) => C): List[C] = for { a_ <- a b_ <- b } yield f(a_, b_) ```
List であれば上記のようになります
--- class: left, top ## `Applicative` Compared to `map` as following
map と比較すると次のような感じです。
--- class: left, top ## `Applicative` Compared to `map` as following ```scala def map[A, B](a: Option[A])(f: (A) => B): Option[B] def map2[A, B, C](a: Option[A], b: Option[B]) (f: (A, B) => C): Option[C] ```
Option 版
--- class: left, top ## `Applicative` Compared to `map` as following ```scala def map[A, B](a: Option[A])(f: (A) => B): Option[B] def map2[A, B, C](a: Option[A], b: Option[B]) (f: (A, B) => C): Option[C] ``` ```scala def map[A, B](a: List[A])(f: (A) => B): List[B] def map2[A, B, C](a: List[A], b: List[B]) (f: (A, B) => C): List[C] ```
List 版
--- class: left, top ## `Applicative` Compared to `map` as following ```scala def map[A, B](a: Option[A])(f: (A) => B): Option[B] def map2[A, B, C](a: Option[A], b: Option[B]) (f: (A, B) => C): Option[C] ``` ```scala def map[A, B](a: List[A])(f: (A) => B): List[B] def map2[A, B, C](a: List[A], b: List[B]) (f: (A, B) => C): List[C] ``` While `map` returns a result from single value and one-parameter function, `map2` returns a result from two values and two-parameters function.
map は一つ値と一引数関数から結果を作るのに対し、
map2 は二つの値と二引数関数から結果をつくります。
--- class: left, top ## `Applicative` Compared to `map` as following ```scala def map[A, B](a: Option[A])(f: (A) => B): Option[B] def map2[A, B, C](a: Option[A], b: Option[B]) (f: (A, B) => C): Option[C] ``` ```scala def map[A, B](a: List[A])(f: (A) => B): List[B] def map2[A, B, C](a: List[A], b: List[B]) (f: (A, B) => C): List[C] ``` While `map` returns a result from single value and one-parameter function, `map2` returns a result from two values and two-parameters function. In other words, `map2` can be used to combine multiple values into a single value.
つまり map2 が使えると、複数の値を合成して一つの値にすることができます。
--- class: left, top ## the `traverse` The reason why `traverse` is so powerful is that it can take advantage of this `Applicative` property.
traverse がなぜ強力なのかと言うと、この Applicative の性質を活用できるからです。
--- class: left, top ## the `traverse` The reason why `traverse` is so powerful is that it can take advantage of this `Applicative` property. Let's look at a concrete example.
具体例を見てみましょう。
--- class: left, top ## the `traverse` `map2` of `Either` returns Left immediately when one of the two values is Left, and applies the function when both values are Right.
Either の map2 は二つの値のいずれかが Left であれば即座に Left を返し、
二つの値の両方が Right であれば関数を適用します。
--- class: left, top ## the `traverse` `map2` of `Either` returns Left immediately when one of the two values is Left, and applies the function when both values are Right. ```scala val e1: Either[String, Int] = Left("error1") val e2: Either[String, Int] = Right(1) ```
Either の map2 は二つの値のいずれかが Left であれば即座に Left を返し、
二つの値の両方が Right であれば関数を適用します。
--- class: left, top ## the `traverse` `map2` of `Either` returns Left immediately when one of the two values is Left, and applies the function when both values are Right. ```scala val e1: Either[String, Int] = Left("error1") val e2: Either[String, Int] = Right(1) ``` ```scala val result = EitherApplicative.map2(e1, e2)(_ + _) println(result) ```
Either の map2 は二つの値のいずれかが Left であれば即座に Left を返し、
二つの値の両方が Right であれば関数を適用します。
--- class: left, top ## the `traverse` `map2` of `Either` returns Left immediately when one of the two values is Left, and applies the function when both values are Right. ```scala val e1: Either[String, Int] = Left("error1") val e2: Either[String, Int] = Right(1) ``` ```scala val result = EitherApplicative.map2(e1, e2)(_ + _) println(result) ``` ```scala Left("error1") // Either[String, Int] ```
Either の map2 は二つの値のいずれかが Left であれば即座に Left を返し、
二つの値の両方が Right であれば関数を適用します。
--- class: left, top ## the `traverse` `map2` of `Either` returns Left immediately when one of the two values is Left, and applies the function when both values are Right. .diff-rm[ ```scala *val e1: Either[String, Int] = `Left("error1")` val e2: Either[String, Int] = Right(1) ``` ] ```scala val result = EitherApplicative.map2(e1, e2)(_ + _) println(result) ``` ```scala Left("error1") // Either[String, Int] ```
Either の map2 は二つの値のいずれかが Left であれば即座に Left を返し、
二つの値の両方が Right であれば関数を適用します。
--- class: left, top ## the `traverse` `map2` of `Either` returns Left immediately when one of the two values is Left, and applies the function when both values are Right. .diff-add[ ```scala *val e1: Either[String, Int] = `Right(2)` val e2: Either[String, Int] = Right(1) ``` ] ```scala val result = EitherApplicative.map2(e1, e2)(_ + _) println(result) ```
Either の map2 は二つの値のいずれかが Left であれば即座に Left を返し、
二つの値の両方が Right であれば関数を適用します。
--- ## the `traverse` `map2` of `Either` returns Left immediately when one of the two values is Left, and applies the function when both values are Right. ```scala val e1: Either[String, Int] = Right(2) val e2: Either[String, Int] = Right(1) ``` ```scala val result = EitherApplicative.map2(e1, e2)(_ + _) println(result) ``` ```scala Right(3) // Either[String, Int] ```
Either の map2 は二つの値のいずれかが Left であれば即座に Left を返し、
二つの値の両方が Right であれば関数を適用します。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => Either.cond(i < 10, i * 10, s"error: $i") } println(result) ``` Let's pass a function that returns Either to the List traverse.
List の traverse に Either を返す関数を渡してみます。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => Either.cond(`i < 10`, i * 10, s"error: $i") } println(result) ``` Let's pass a function that returns Either to the List traverse. If the value is less than 10, the function returns `Right`, so it returns `Right` for all elements of the list.
10 より小さい値であれば Right を返すので、全ての要素で Right を返します。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => Either.cond(i < 10, i * 10, s"error: $i") } println(result) ``` ```scala Right(List(10, 20, 30, 40, 50)) ``` Therefore, the result is a `List` wrapped in `Right`.
従って、結果は Right に包まれた List になります。
--- class: left, top ## the `traverse` .diff-rm[ ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => * Either.cond(`i < 10`, i * 10, s"error: $i") } println(result) ``` ] Now let's change the function to return `Right` for any value less than 3.
今度は 3 より小さい値であれば Right を返すように変えてみましょう。
--- class: left, top ## the `traverse` .diff-add[ ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => * Either.cond(`i < 3`, i * 10, s"error: $i") } println(result) ``` ] Now let's change the function to return `Right` for any value less than 3.
今度は 3 より小さい値であれば Right を返すように変えてみましょう。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => Either.cond(i < 3, i * 10, s"error: $i") } println(result) ``` ```scala Left("error: 3") ``` This time we see that the result is `Left`.
今度は結果が Left になる事が確認できます。
--- class: left, top ## the `traverse` .diff-add[ ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => * println(i) Either.cond(i < 3, i * 10, s"error: $i") } println(result) ``` ] Let's output the progress as the test.
試しに途中経過を出力してみましょう。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) Either.cond(i < 3, i * 10, s"error: $i") } println(result) ``` ```scala 1 2 3 Left("error: 3") ``` We can see that up to 3 is processed, while 4 and 5 are not processed.
3 まで処理されて結果が返され、4, 5 は処理されていない事がわかります。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) Either.cond(i < 3, i * 10, s"error: $i") } println(result) ``` ```scala 1 2 3 Left("error: 3") ``` In this way, if we pass a function that returns `Either` or `Option` to `traverse`, We can write a loop that terminates processing in the middle, such as `break`.
この様に、traverse に Either や Option を返す関数を渡すと
break のような途中で処理を打ち切るループも書くことができます。
--- class: left, top ## the `traverse` .diff-rm[ ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) * `Either.cond`(i < 3, i * 10, s"error: $i") } println(result) ``` ] ```scala 1 2 3 Left("error: 3") ``` Now let's use `Validated` instead of `Either`.
今度は Either の代わりに Validated を使ってみましょう。
--- class: left, top ## the `traverse` .diff-add[ ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) * `Validated.condNel`(i < 3, i * 10, s"error: $i") } println(result) ``` ] Now let's use `Validated` instead of `Either`.
今度は Either の代わりに Validated を使ってみましょう。
--- class: left, top ## the `traverse` .diff-add[ ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) * `Validated.condNel`(i < 3, i * 10, s"error: $i") } println(result) ``` ] Now let's use `Validated` instead of `Either`. `Validated` is a data structure that represents either `Invalid` or `Valid` like `Either`.
Validated は Either のように Invalid または Valid のどちらかを表すデータ構造です。
--- class: left, top ## the `traverse` .diff-add[ ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) * `Validated.condNel`(i < 3, i * 10, s"error: $i") } println(result) ``` ] Now let's use `Validated` instead of `Either`. `Validated` is a data structure that represents either `Invalid` or `Valid` like `Either`. `Validated` differs from `Either` in that the `map2` of `Validated` aggregates the values of `Invalid`.
Either と異なる点は、Validated の map2 は Invalid が持つ値を集約するという事です。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) Validated.condNel(i < 3, i * 10, s"error: $i") } println(result) ``` ```scala 1 2 3 4 5 Invalid(List("error: 3", "error: 4", "error: 5")) ``` Because of this difference, passing a function that returns `Validated` to `traverse` will aggregate all `Invalid` values.
その違いのため、traverse に Validated を返す関数を渡すと全ての Invalid値を集約します。
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) `Either.cond`(i < 3, i * 10, s"error: $i") } println(result) ``` ```scala 1 2 3 Left("error: 3") ``` In the case of `Either`, the loop ends when an error occurs.
Either の時はエラーが発生した時点でループが終了したのに対し、
--- class: left, top ## the `traverse` ```scala val list = List(1, 2, 3, 4, 5) val result = list.traverse { i => println(i) `Validated.condNel`(i < 3, i * 10, s"error: $i") } println(result) ``` ```scala 1 2 3 4 5 Invalid(List("error: 3", "error: 4", "error: 5")) ``` Whereas in the case of `Validated`, the loop continues to the end and aggregates the errors.
Validated の時は最後までループしエラーを集約していることが見て取れます。
--- class: left, top ## the `traverse` In this way, The `traverse` allows us to control the behavior of the loop by using the `Applicative` concrete type.
この様に traverse は Applicative の具象型によってループの挙動を制御できます。
--- class: left, top ## the `traverse` In this way, The `traverse` allows us to control the behavior of the loop by using the `Applicative` concrete type. ```scala def validateUser(user: User): Validated[Errors, User] = ... ``` ```scala def validateUsers(users:List[User]):Validated[Errors, List[User]]= users.traverse(validateUser) ``` As a side note, as in the example, if you define validation logic for a single user, you can easily get validation logic for multiple users using `traverse`.
また余談ですが、例のように、単一のユーザに対する validation ロジックを定義しておけば、
traverse を使えば簡単に複数のユーザに対する validation ロジックを手に入れられます。
--- class: left, top ## the `traverse` The `Applicative` can also be compose in various ways.
また、Applicative は様々な方法で合成することができます。
--- class: left, top ## the `traverse` The `Applicative` can also be compose in various ways. For example, if you want to calculate the sum value and the product value in a List, we can compose each of them as a tuple.
例えば List の中から和と積を計算したい時、それぞれをタプルで合成できます。
--- class: left, top ## the `traverse` The `Applicative` can also be compose in various ways. For example, if you want to calculate the sum value and the product value in a List, we can compose each of them as a tuple. ```scala val list = List(1, 2, 3, 4, 5) val Const((sum, product)) = list.traverse { i => Const((Sum(i), Product(i))) } println(s"sum: $sum") println(s"product: $product") ```
例えば List の中から和と積を計算したい時、それぞれをタプルで合成できます。
--- class: left, top ## the `traverse` The `Applicative` can also be compose in various ways. For example, if you want to calculate the sum value and the product value in a List, we can compose each of them as a tuple. ```scala val list = List(1, 2, 3, 4, 5) val Const((sum, product)) = list.traverse { i => Const((Sum(i), Product(i))) } println(s"sum: $sum") println(s"product: $product") ``` ```scala sum: 15 product: 120 ```
例えば List の中から和と積を計算したい時、それぞれをタプルで合成できます。
--- class: left, top ## the `traverse` The `Applicative` can also be compose in various ways. For example, if you want to calculate the sum value and the product value in a List, we can compose each of them as a tuple. ```scala val list = List(1, 2, 3, 4, 5) val Const((sum, product)) = list.traverse { i => Const((Sum(i), Product(i))) } println(s"sum: $sum") println(s"product: $product") ``` ```scala sum: 15 product: 120 ``` The important point here is that the process of summarizing and producting can be defined separately from the loop.
ここでのポイントは、和を求める処理と積を求める処理を、
ループとは別にそれぞれ定義できることです。
--- class: left, top ## the `traverse` The `Applicative` can also be compose in various ways. In other words, even complex and huge loops can be broken down into smaller pieces by finding individual `Applicative` values.
つまり複雑で巨大なループ処理も、
個々の Applicative な値を見つける事で小さく分解することができます。
--- class: left, top ## the `traverse` The `Applicative` can also be compose in various ways. In other words, even complex and huge loops can be broken down into smaller pieces by finding individual `Applicative` values. The ability to break down the loop process into smaller pieces makes it easier to test, and easier to add other processing later.
小さく分解できるという事は、テストも容易になり、
後に別の処理を加えることも容易になっていきます。
--- class: left, top ## the `traverse` By using various Applicative concrete types, traverse can express all the looping operations realized by the Iterator pattern.
様々な Applicative の具象型を使うことで
traverse は the Iterator pattern が実現するループ処理について全て表現する事ができます。
--- class: left, top ## the `traverse` By using various Applicative concrete types, traverse can express all the looping operations realized by the Iterator pattern. The `Applicative` property makes it possible to decompose the components of a complex loop to increase its modularity.
Applicative の性質を利用することで、
複雑なループの構成要素を分解しモジュラリティを高める事が可能になります。
--- class: left, top ## the Essence of the Iterator pattern For the evidence that rewriting is possible and more complex concrete examples, see, [The Essence of the Iterator pattern](http://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf)(J Gibbons, BCS Oliveira, 2009) (PDF) for those who are interested.
このセッションで紹介した書き換えが可能な根拠やより複雑な具体例などは、
http://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf
に詳しく書かれているので、興味がある方はぜひご参照ください。
--- class: left, top ## the Essence of the Iterator pattern For the evidence that rewriting is possible and more complex concrete examples, see, [The Essence of the Iterator pattern](http://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf)(J Gibbons, BCS Oliveira, 2009) (PDF) for those who are interested. Also, please refer to the following supplementary article on the implementation aspects of `traverse` that were not covered in this session. I have written a supplementary article titled ["`map` を使いこなせるあなたに `traverse`"](https://zenn.dev/gakuzzzz/articles/81cd723a36067f). Please refer to this article if you would like to know more about the implementation of `traverse`.
また、このセッション中で紹介しきれなかった traverse の実装面について
https://zenn.dev/gakuzzzz/articles/81cd723a36067f
補足記事を書きましたので、よかったらこちらもご参照ください。
--- class: left, top ## Summary - Loops are important in programming.
プログラミングにおいてループは重要です。
--- class: left, top ## Summary - Loops are important in programming. - A design pattern was created for the process of iterating elements over data structures.
データ構造に対して要素をイテレートする処理はパターンが生まれました。
--- class: left, top ## Summary - Loops are important in programming. - A design pattern was created for the process of iterating elements over data structures. - The iteration represented by this pattern can be represented by `traverse`.
このパターンで表現されるイテレーションは traverse で表現することができます。
--- class: left, top ## Summary - Loops are important in programming. - A design pattern was created for the process of iterating elements over data structures. - The iteration represented by this pattern can be represented by `traverse`. - The `traverse` takes advantage of the `Applicative` property
traverse は Applicative の性質を活用します。
--- class: left, top ## Summary - Loops are important in programming. - A design pattern was created for the process of iterating elements over data structures. - The iteration represented by this pattern can be represented by `traverse`. - The `traverse` takes advantage of the `Applicative` property - Modularity is increased by organizing the contents of the iteration by `Applicative`.
イテレーションの中身を Applicative で整理する事によってモジュラリティが高まります。
--- class: left, top ## Summary - Loops are important in programming. - A design pattern was created for the process of iterating elements over data structures. - The iteration represented by this pattern can be represented by `traverse`. - The `traverse` takes advantage of the `Applicative` property - Modularity is increased by organizing the contents of the iteration by `Applicative`. - let's control codes, let's control `traverse`
traverse を制して code を制しましょう。
--- class: center, middle ## Any questions?