LL脳がscalaの勉強を始めたよ その85


Scalaコップ本23章の残りをやってしまいますかね(`・ω・´)、今回は高階関数からfor式への逆変換とfor式の一般化をやりますよ

逆方向への変換

前回はfor式からmapやflatMap、filterの各高階関数への変換についてやったのですがこの逆パターンも可能だYO!ってことでそいつをやってみますかね。for式で書き換えることで場合によってはコードを整理することができたりするかもですね(`・ω・´)

それではmap、flatMap、filterの各高階関数をfor式に変換してやりますデス

// 変換用オブジェクトを定義しますよ
object Demo {
  // mapをfor式で表現します
  def map[A, B](xs:List[A], f:A => B):List[B] =
    // リストの各要素を反復処理します
    for(x <-xs) yield f(x)

  // flatMapをfor式で表現します
  def flatMap[A, B](xs:List[A], f:A => List[B]):List[B] =
    // 各要素を展開して新しいリストを構築しておりますね
    for(x <- xs; y <- f(x)) yield y
    
  // filterをfor式で表現します
  def filter[A](xs:List[A], p:A => Boolean):List[A] =
    // for式内でフィルタしてやります
    for(x <- xs if p(x)) yield x
}

//// 実行してやりますよ
// まずはmapです
scala> Demo.map(List(1,2,3,4,5), (x:Int) => {x + 1})
res12: List[Int] = List(2, 3, 4, 5, 6)

// 次にflatMapです
scala> Demo.flatMap(List(1,2,3,4,5), (x:Int) => { List(x + 1) })
res16: List[Int] = List(2, 3, 4, 5, 6)

// 最後にfilterです
scala> Demo.filter(List(1,2,3,4,5), (x:Int) => { x < 3})        
res18: List[Int] = List(1, 2)

…とこんな感じで各種高階関数をfor式で表現してみましたが、実際のところ上記処理はコンパイラ様によってmap、flatMap、filterのネイティブな処理に書き換えられてしまうのです(´・ω・`)まあ、コードの読みやすさとかそういう部分で使うのが良さそうですね。

for式の一般化

これまでやってきたとおりfor式はmap・flatMap・filterとループ時のforeachに変換されるので、これらの各メソッドを持つ型であればfor式を利用することができるみたいです。例えばこれまでサンプルで利用してきたリストや配列の他にも、範囲・イテレータ・ストリーム・集合なんかでも使えるみたいです(`・ω・´)

また、当然各種メソッドを定義することで自分で構築した型でも利用出来るのですが、map・flatMap・filter・foreachを全て定義せずともこれらのサブセットを定義するだけで一部のfor式とforループを利用出来るようになるとのこと。せっかくなのでその条件を列挙してみますよ(`・ω・´)

  • mapだけを定義
    • 1個のジェネレータだけで構成されるfor式が使える
  • mapとflatMapを定義
    • 複数のジェネレータから構成されるfor式を使える
  • (他の定義に加えて)filterを定義
    • for式内のフィルタ式を使える
  • foreachを定義
    • forループを利用可能(じぇねれーたは複数OK)

内容的には前回のfor式の変換で置き換えた各種メソッドとの対応関係に沿うかんじですな(´・ω・`)

for式の変換と型チェック

for式の変換は型チェックの前に行われるそうなので、for式を展開した結果だけが型チェックを通ればよいみたいです。なのでそこらへんの型チェックを意識せずに自由奔放なコードがかけるとのことです。またscalaではfor式と同様にmap・flatMap・filter・foreachに特定の型シグネチャーを持つことを要求しないとのことです(´・ω・`)

ただし各周高階メソッドでは型シグネチャーを持つことを要求されないとはいえ、典型的な形があるとのこと…なにやら一般的な意図があるからウンヌン…とのことですが何かはわからんす(´・ω・`)

とりあえず典型的な形としては次のようなパラメータ化されたコレクションみたいです。どうもScala的にはこれが自然な形なんだとか(´・ω・`)

abstract class C[A] {
  def map[B](f:A=>B):C[B]
  def flatMap[B](f:A=>C[B]):C[B]
  def filter(p:A => Boolean):C[A]
  def foreach(b:A => Unit):Unit
}

上記で表現される内容を箇条書きでまとめてみると下記のようになりますね…まあそのままを日本語でかきくだすだけですが(´・ω・`)

  • mapは関数をパラメータとして取ってコレクション型を返す
    • パラメータとしての関数はコレクションの要素型をとって、ソレとはことなる型の値を生成する
  • flatMapは関数をパラメータとして取ってコレクション型を返す
    • パラメータとしての関数は要素型の値をとって、関数によって生成された要素を集めたコレクションを返す
  • filterメソッドは関数をパラメータとして取ってコレクション型を返す
    • パラメータとしての関数は要素型の値をとってBooleanを返す述語関数
  • foreachは関数をパラメータとして取って値を返さない(Unit)
    • パラメータとしての関数は何らかの型の値をとってUnit

どうやら上記のうち前半3つは関数型プログラミングでいうモナド概念に近いものみたいとのことです。なので上記3つで書き換えられるfor式はモナドのための構文と言えるとかとか…(´ε`;)ウーン…モナドがわかってないからイマイチ良く解らん話ですな(´・ω・`)

モナド

コップ本によればモナドは、コレクション、状態やI/Oの操作、バックトラック、トランザクション等の様々なタイプの計算を行うことが出来る概念で、mapやflatMapやfilterに加えて「ユニット」コンストラクターによって特徴付けられるものとのことです。ちなみに「ユニット」コンストラクターとは、オブジェクト指向では単純なインスタンスコンストラクターかファクトリーメソッドのことだとか…(´・ω・`)いまいち分からんくなったのでwikipediaさんでも覗いてみますかね

関数型言語において参照透過性を維持しつつ手続き型的な記述を行なうための枠組み”→wikipedia:モナド_(プログラミング)

…適当にさらってみたところ「関数の入れ子で順序処理を表現して「副作用」をパッケージ化する」ってのがモナドってことかしら?いまいち理解がおっついていないので、時間があるときにでもHaskell様に触れてみるのがいいかもですね(´・ω・`)

モナドは便利やでー、とたまに聞くのでしっかり抑えておかないとな...

そしてfor式に戻ってみる

モナドって単語におっついていないですが、コップ本的にはfor式の表現力の高さは素晴らしいから、使うガイイYO!ってことみたいです。特に非同期I/Oとかで重要な役割があったり、オプション値の代替記法としても使える便利ツールだからmapやflatMapやfilterが出てくる箇所では型要素の簡潔な操作方法の実現にfor式の利用を検討するおがいいですよ、とのことでした。


。。。やっぱり型関連は弱いからしっかりなれないとなぁ、と実感中です(´・ω・`)

いじょー

後半失速気味だったけども23章はコレで終わりです。次は24章の抽出子とやらに入っていきますよー