your programing

flatMap / Map 변환에 대한 이해를위한 것과 혼동

lovepro 2020. 10. 4. 12:56
반응형

flatMap / Map 변환에 대한 이해를위한 것과 혼동


나는 정말로 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앞서 언급 한 두 가지 방법을 모두 제공 하는 모든 것이 a라고 할 수 있다고 가정 하고 내부 유형이있는 a를 의미 monad하기 위해 기호 M[A]사용할 것 입니다.monadA

흔히 볼 수있는 모나드는 다음과 같습니다.

  • 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여기서 왼쪽의 "바운드 기호"는 매개 변수로 인수 함수에 전달됩니다 (what 이전에 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. 하나만있는 for-expression 은 인수로 전달 된 표현식이 <-있는 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식 : A는 List남아 List의 조작에 의해 변환 된 내용으로 yield.

반면에의 각 바인딩 선 for은 연속 된의 구성 일 뿐이며 monads단일 "외부 모양"을 유지하려면 "평평하게 만들어야"합니다.

잠시 동안 각 내부 바인딩이 map호출 로 변환 되었지만 오른손이 동일한 A => M[B]기능이라고 가정 M[M[B]]하면 이해의 각 줄에 대해 a 끝날 것입니다 .
전체 for구문 의 의도 는 결론 변환을 수행 A => M[B] 있는 최종 map연산을 추가하여 연속적인 모나드 연산 (즉, "모나드 모양"에서 값을 "리프트"하는 연산)의 연결을 쉽게 "평탄화" 하는 것입니다 .

이것이 기계적 방식으로 적용되는 번역 선택의 논리, 즉 n flatMap단일 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이해를 통해 유지되므로 Listin으로 시작 company.branches하고 List.
대신 내부 유형이 변경되고 다음 yield표현식에 의해 결정됩니다.customer.value: Int

valueList 이어야합니다 List[Int]


나는 스칼라 메가 마인드가 아니므로 자유롭게 나를 수정하십시오. 그러나 이것이 내가 flatMap/map/for-comprehension사가를 나 자신에게 설명하는 방법입니다 !

To understand for comprehension and it's translation to scala's map / flatMap we must take small steps and understand the composing parts - map and flatMap. But isn't scala's flatMap just map with flatten you ask thyself! if so why do so many developers find it so hard to get the grasp of it or of for-comprehension / flatMap / map. Well, if you just look at scala's map and flatMap signature you see they return the same return type M[B] and they work on the same input argument A (at least the first part to the function they take) if that's so what makes a difference?

Our plan

  1. Understand scala's map.
  2. Understand scala's flatMap.
  3. Understand scala's for comprehension.`

Scala's map

scala map signature:

map[B](f: (A) => B): M[B]

But there is a big part missing when we look at this signature, and it's - where does this A comes from? our container is of type A so its important to look at this function in the context of the container - M[A]. Our container could be a List of items of type A and our map function takes a function which transform each items of type A to type B, then it returns a container of type B (or M[B])

Let's write map's signature taking into account the container:

M[A]: // We are in M[A] context.
    map[B](f: (A) => B): M[B] // map takes a function which knows to transform A to B and then it bundles them in M[B]

Note an extremely highly highly important fact about map - it bundles automatically in the output container M[B] you have no control over it. Let's us stress it again:

  1. map chooses the output container for us and its going to be the same container as the source we work on so for M[A] container we get the same M container only for B M[B] and nothing else!
  2. map does this containerization for us we just give a mapping from A to B and it would put it in the box of M[B] will put it in the box for us!

You see you did not specify how to containerize the item you just specified how to transform the internal items. And as we have the same container M for both M[A] and M[B] this means M[B] is the same container, meaning if you have List[A] then you are going to have a List[B] and more importantly map is doing it for you!

Now that we have dealt with map let's move on to flatMap.

Scala's flatMap

Let's see its signature:

flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]

You see the big difference from map to flatMap in flatMap we are providing it with the function that does not just convert from A to B but also containerizes it into M[B].

why do we care who does the containerization?

So why do we so much care of the input function to map/flatMap does the containerization into M[B] or the map itself does the containerization for us?

You see in the context of for comprehension what's happening is multiple transformations on the item provided in the for so we are giving the next worker in our assembly line the ability to determine the packaging. imagine we have an assembly line each worker does something to the product and only the last worker is packaging it in a container! welcome to flatMap this is it's purpose, in map each worker when finished working on the item also packages it so you get containers over containers.

The mighty for comprehension

Now let's looks into your for comprehension taking into account what we said above:

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

What have we got here:

  1. mkMatcher returns a container the container contains a function: String => Boolean
  2. The rules are the if we have multiple <- they translate to flatMap except for the last one.
  3. As f <- mkMatcher(pat) is first in sequence (think assembly line) all we want out of it is to take f and pass it to the next worker in the assembly line, we let the next worker in our assembly line (the next function) the ability to determine what would be the packaging back of our item this is why the last function is map.
  4. The last g <- mkMatcher(pat2) will use map this is because its last in assembly line! so it can just do the final operation with map( g => which yes! pulls out g and uses the f which has already been pulled out from the container by the flatMap therefore we end up with first:

    mkMatcher(pat) flatMap (f // pull out f function give item to next assembly line worker (you see it has access to f, and do not package it back i mean let the map determine the packaging let the next assembly line worker determine the container. mkMatcher(pat2) map (g => f(s) ...)) // as this is the last function in the assembly line we are going to use map and pull g out of the container and to the packaging back, its map and this packaging will throttle all the way up and be our package or our container, yah!


The rationale is to chain monadic operations which provides as a benefit, proper "fail fast" error handling.

It is actually pretty simple. The mkMatcher method returns an Option (which is a Monad). The result of mkMatcher, the monadic operation, is either a None or a Some(x).

Applying the map or flatMap function to a None always returns a None - the function passed as a parameter to map and flatMap is not evaluated.

Hence in your example, if mkMatcher(pat) returns a None, the flatMap applied to it will return a None (the second monadic operation mkMatcher(pat2) will not be executed) and the final mapwill again return a None. In other words, if any of the operations in the for comprehension, returns a None, you have a fail fast behavior and the rest of the operations are not executed.

This is the monadic style of error handling. The imperative style uses exceptions, which are basically jumps (to a catch clause)

A final note: the patterns function is a typical way of "translating" an imperative style error handling (try...catch) to a monadic style error handling using Option


This can be traslated as:

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)  // for every element from this [list, array,tuple]
    g <- mkMatcher(pat2) // iterate through every iteration of pat
} yield f(s) && g(s)

Run this for a better view of how its expanded

def match items(pat:List[Int] ,pat2:List[Char]):Unit = for {
        f <- pat
        g <- pat2
} println(f +"->"+g)

bothMatch( (1 to 9).toList, ('a' to 'i').toList)

results are:

1 -> a
1 -> b
1 -> c
...
2 -> a
2 -> b
...

This is similar to flatMap - loop through each element in pat and foreach element map it to each element in pat2


First, mkMatcher returns a function whose signature is String => Boolean, that's a regular java procedure which just run Pattern.compile(string), as shown in the pattern function. Then, look at this line

pattern(pat) map (p => (s:String) => p.matcher(s).matches)

The map function is applied to the result of pattern, which is Option[Pattern], so the p in p => xxx is just the pattern you compiled. So, given a pattern p, a new function is constructed, which takes a String s, and check if s matches the pattern.

(s: String) => p.matcher(s).matches

Note, the p variable is bounded to the compiled pattern. Now, it's clear that how a function with signature String => Boolean is constructed by mkMatcher.

Next, let's checkout the bothMatch function, which is based on mkMatcher. To show how bothMathch works, we first look at this part:

mkMatcher(pat2) map (g => f(s) && g(s))

Since we got a function with signature String => Boolean from mkMatcher, which is g in this context, g(s) is equivalent to Pattern.compile(pat2).macher(s).matches, which returns if the String s matches pattern pat2. So how about f(s), it's same as g(s), the only difference is that, the first call of mkMatcher uses flatMap, instead of map, Why? Because mkMatcher(pat2) map (g => ....) returns Option[Boolean], you will get a nested result Option[Option[Boolean]] if you use map for both call, that's not what you want .

참고URL : https://stackoverflow.com/questions/14598990/confused-with-the-for-comprehension-to-flatmap-map-transformation

반응형