1. ホーム
  2. scala

[解決済み] flatMap/Map変換のfor-comprehensionで迷う。

2023-04-08 10:53:19

質問

私は本当にMapとFlatMapを理解していないようです。私が理解していないのは、for-comprehensionがmapとflatMapへのネストされた呼び出しのシーケンスであることです。次の例は Scalaで学ぶ関数型プログラミング

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
            f <- mkMatcher(pat)
            g <- mkMatcher(pat2)
 } yield f(s) && g(s)

は以下のように変換されます。

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = 
         mkMatcher(pat) flatMap (f => 
         mkMatcher(pat2) map (g => f(s) && g(s)))

mkMatcherメソッドは以下のように定義されています。

  def mkMatcher(pat:String):Option[String => Boolean] = 
             pattern(pat) map (p => (s:String) => p.matcher(s).matches)

そして、パターンメソッドは次のようになります。

import java.util.regex._

def pattern(s:String):Option[Pattern] = 
  try {
        Some(Pattern.compile(s))
   }catch{
       case e: PatternSyntaxException => None
   }

ここでmapとflatMapを使う根拠について、誰かが光を当ててくれると嬉しいのですが。

どのように解決するのですか?

TL;DR 最終例へ直接進む

再録してみる

定義

この for の内包は、構文のショートカットで flatMapmap を読みやすく、推論しやすくしたものです。

物事を少し単純化して、すべての class を呼び出すことができると仮定します。 monad という記号を使用します。 M[A] を意味するために monad を意味し、内部型は A .

よく見かけるモナドには以下のようなものがあります。

  • List[String] ここで
    • M[X] = List[X]
    • A = String
  • Option[Int] ここで
    • M[X] = Option[X]
    • A = Int
  • Future[String => Boolean] ここで
    • M[X] = Future[X]
    • A = (String => Boolean)

map と flatMap

汎用モナドで定義される M[A]

 /* applies a transformation of the monad "content" mantaining the 
  * monad "external shape"  
  * i.e. a List remains a List and an Option remains an Option 
  * but the inner type changes
  */
  def map(f: A => B): M[B] 

 /* applies a transformation of the monad "content" by composing
  * this monad with an operation resulting in another monad instance 
  * of the same type
  */
  def flatMap(f: A => M[B]): M[B]

など

  val list = List("neo", "smith", "trinity")

  //converts each character of the string to its corresponding code
  val f: String => List[Int] = s => s.map(_.toInt).toList 

  list map f
  >> List(List(110, 101, 111), List(115, 109, 105, 116, 104), List(116, 114, 105, 110, 105, 116, 121))

  list flatMap f
  >> List(110, 101, 111, 115, 109, 105, 116, 104, 116, 114, 105, 110, 105, 116, 121)

式に対して

  1. を使った式の各行は <- 記号に変換されます。 flatMap の呼び出しに変換されますが、最後の行は最後の map 呼び出しに変換され、左側の "bound symbol" が引数関数のパラメータとして渡されます (以前は f: A => M[B] ):

    // The following ...
    for {
      bound <- list
      out <- f(bound)
    } yield out
    
    // ... is translated by the Scala compiler as ...
    list.flatMap { bound =>
      f(bound).map { out =>
        out
      }
    }
    
    // ... which can be simplified as ...
    list.flatMap { bound =>
      f(bound)
    }
    
    // ... which is just another way of writing:
    list flatMap f
    
    
  2. を1つだけ持つfor式。 <- に変換されます。 map の呼び出しに変換されます。

    // The following ...
    for {
      bound <- list
    } yield f(bound)
    
    // ... is translated by the Scala compiler as ...
    list.map { bound =>
      f(bound)
    }
    
    // ... which is just another way of writing:
    list map f
    
    

さて、本題に入ります。

ご覧のように map の操作によって、元の monad であるため、同じことが yield の式でも同様です。 ListList の中の操作によって変換された内容で yield .

一方 for は単に連続した monads であり、単一の外形を維持するために平坦化されなければならない。

仮に、それぞれの内部バインディングが map の呼び出しで、右側は同じ A => M[B] 関数であった場合、最終的に M[M[B]] になってしまいます。

全体の意図 for 構文は、連続するモナド操作(つまり、"モナド形状" の値を持ち上げる操作)の連結を簡単に"フラット化" するためのものです。 A => M[B] を加えたもの)を連結し、最後に map という操作を追加しました。 おそらく は、結論となる変換を実行します。

という機械的な方法で適用される翻訳を選択する論理を説明することができたと思います。 n flatMap ネストされた呼び出しは、1つの map の呼び出しで終わります。

巧妙な説明の例

の表現力を示すことを意図しています。 for 構文

case class Customer(value: Int)
case class Consultant(portfolio: List[Customer])
case class Branch(consultants: List[Consultant])
case class Company(branches: List[Branch])

def getCompanyValue(company: Company): Int = {

  val valuesList = for {
    branch     <- company.branches
    consultant <- branch.consultants
    customer   <- consultant.portfolio
  } yield (customer.value)

  valuesList reduce (_ + _)
}

のタイプはわかりますか? valuesList ?

すでに述べたように、形状は monad の形状は理解を通じて維持されるので、まず最初に Listcompany.branches で終わる必要があり List .

代わりに内側の型が変わり、その型は yield 表現:どのような customer.value: Int

valueList であるべきです。 List[Int]