Mylone z przekształceniem flatMap/Map

Naprawdę nie rozumiem Map i FlatMap. To, czego nie rozumiem, to to, w jaki sposób rozumienie jest sekwencją zagnieżdżonych wywołań map i flatMap. Poniższy przykład pochodzi z programowania funkcyjnego w Scali

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

Tłumaczy się na

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

Metoda mkMatcher jest zdefiniowana w następujący sposób:

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

I metoda wzorcowa jest następująca:

import java.util.regex._

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

Byłoby świetnie, gdyby ktoś mógł rzucić trochę światła na uzasadnienie korzystania z mapy i flatMap tutaj.

Author: sc_ray, 2013-01-30

5 answers

TL; DR przejdź bezpośrednio do ostatecznego przykładu

Spróbuję podsumować

Definicje

The for comprehension jest skrótem składniowym do łączenia flatMap i map w sposób łatwy do odczytania i rozumowania.

Trochę uprośćmy i załóżmy, że każda class, która dostarcza obie wyżej wymienione metody, może być nazwana monad i użyjemy symbolu M[A], aby oznaczać monad z typem wewnętrznym A.

Przykłady

Niektóre powszechnie widziane monady

  • List[String] gdzie
    • M[_]: List[_]
    • A: String
  • Option[Int] gdzie
    • M[_]: Option[_]
    • A: Int
  • Future[String => Boolean] gdzie
    • M[_]: Future[_]
    • A: String => Boolean

Mapa i flatMap

Zdefiniowany w ogólnym monadzie 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]

Np.

  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)

Na wyrażenie

  1. Każda linia w wyrażeniu używająca symbolu <- jest tłumaczona na wywołanie flatMap, z wyjątkiem ostatniej linii, która jest tłumaczona na wywołanie końcowe map, gdzie "związany symbol" po lewej stronie jest przekazywany jako parametr do funkcji argumentu (co wcześniej nazywaliśmy 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. Wyrażenie for z tylko jednym {[23] } jest konwertowane do wywołania map z wyrażeniem przekazanym jako argument:

    // 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
    

Teraz do punktu

Jak widać, operacja map zachowuje" kształt " oryginalnego monad, tak samo dzieje się z wyrażeniem yield: A List pozostaje List z treścią przekształconą przez operację w yield.

Z drugiej strony każda linia wiążąca w for jest tylko kompozycją kolejnych monads, które muszą być "spłaszczone", aby utrzymać pojedynczy "zewnętrzny kształt".

Przypuśćmy, że w momencie, gdy każde wewnętrzne Wiązanie zostało przetłumaczone na wywołanie map, ale prawa ręka była tą samą funkcją A => M[B], kończyłbyś M[M[B]] dla każdej linii w zrozumieniu.
Intencją całej składni for jest łatwe" spłaszczenie " konkatenacji kolejnych operacji monadycznych (tj. operacji, które "podnoszą" wartość w "monadycznym kształcie": A => M[B]), z dodaniem ostatecznej operacji map, która ewentualnie wykona końcową transformację.

I hope wyjaśnia to logikę wyboru tłumaczenia, który jest stosowany w sposób mechaniczny, czyli: n flatMap zagnieżdżone wywołania zakończone pojedynczym wywołaniem map.

W 2007 roku został powołany do kadry na Puchar Narodów Afryki 2007/2008.]} Ma na celu pokazanie wyrazistości składni 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)

  valueList reduce (_ + _)
}

Możesz odgadnąć rodzaj valuesList?

Jak już wspomniano, kształt monad jest utrzymywany przez zrozumienie, więc zaczynamy od List W company.branches i musimy zakończyć na List.
Zamiast tego Typ wewnętrzny zmienia się i jest określony przez wyrażenie yield, które jest customer.value: Int

valueList powinno być List[Int]

 159
Author: pagoda_5b,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-06 08:51:24

Uzasadnieniem jest łańcuch operacji monadycznych, który zapewnia jako korzyść właściwą obsługę błędów "Fail fast".

To całkiem proste. Metoda mkMatcher zwraca Option (która jest monadą). Wynikiem mkMatcher, operacji monadycznej, jest None lub Some(x).

Zastosowanie funkcji map lub flatMap do None zawsze zwraca None - funkcja przekazana jako parametr do map i flatMap nie jest oceniana.

Stąd w twoim przykładzie, jeśli mkMatcher(pat) zwraca None, zastosowana do niej flatMap zwróci None (druga monadyczna operacja mkMatcher(pat2) nie zostanie wykonana), a ostateczna mapponownie zwróci None. Innymi słowy, jeśli którakolwiek z operacji w For comprehension, zwróci None, masz zachowanie Fail fast, a reszta operacji nie zostanie wykonana.

Jest to monadyczny styl obsługi błędów. Styl imperatywny używa wyjątków, które są w zasadzie skokami (do klauzuli catch) {]}

Uwaga końcowa: funkcja patterns jest typowym sposobem "tłumaczenia" imperatywnej obsługi błędów stylu (try...catch) do obsługi błędów stylu monadycznego za pomocą Option

 3
Author: Bruno Grieder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-30 08:22:22

Nie jestem Scala mega umysłem, więc nie krępuj się mnie poprawić, ale tak wyjaśniam sobie sagę flatMap/map/for-comprehension!

Aby zrozumieć for comprehension i to tłumaczenie na scala's map / flatMap musimy podjąć małe kroki i zrozumieć komponujące się części - map i flatMap. Ale czy to nie jest scala's flatMap tylko map z flatten pytasz siebie! jeśli tak, to dlaczego tak wielu deweloperów tak trudno jest zrozumieć to lub for-comprehension / flatMap / map. Cóż, jeśli spojrzysz na podpis Scali map i flatMap, zobaczysz, że zwracają ten sam typ zwrotu {[15] } i pracują na tym samym argumencie wejściowym A (przynajmniej pierwsza część funkcji, którą biorą) jeśli tak, to co robi różnicę?

Nasz plan

  1. zrozumieć scalę map.
  2. zrozumieć scalę flatMap.
  3. zrozumieć scalę for comprehension.`

Mapa Scali

Podpis mapy Scala:

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

Ale jest duża część brakuje, gdy patrzymy na ten podpis, i to-gdzie to A skąd? nasz kontener jest typu A, więc ważne jest, aby spojrzeć na tę funkcję w kontekście kontenera - M[A]. Nasz kontener może być List elementów typu A, a nasza map funkcja przyjmuje funkcję, która przekształca każdy element typu A do typu B, a następnie zwraca kontener typu B (lub M[B])

Zapiszmy podpis mapy biorąc pod uwagę kontener:

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]

Zwróć uwagę na niezwykle ważny fakt o map - łączy automatycznie w pojemniku wyjściowym M[B] nie masz nad nim kontroli. Podkreślmy to jeszcze raz:

  1. map wybiera kontener wyjściowy dla nas i będzie on tym samym kontenerem co źródło, nad którym pracujemy, więc dla M[A] kontenera otrzymujemy ten sam M kontener tylko dla B M[B] i nic więcej!
  2. map czy ta konteneryzacja dla nas wystarczy dać mapowanie z A do B i umieścić go w polu / Align = "left" /

Widzisz, że nie określiłeś sposobu containerize elementu, który określiłeś, jak przekształcić wewnętrzne elementy. A ponieważ mamy ten sam kontener M zarówno dla M[A], jak i M[B], oznacza to, że M[B] jest tym samym kontenerem, co oznacza, że jeśli masz List[A], to będziesz miał List[B] i co ważniejsze map robi to za Ciebie!

Teraz, gdy mamy do czynienia z map przejdźmy do flatMap.

Scala ' s flatMap

Zobaczmy jego podpis:

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

Widzisz dużą różnicę między mapą a flatMap w flatMap zapewniamy jej funkcję, która nie tylko konwertuje z A to B, ale także konteneruje ją do M[B].

Dlaczego obchodzi nas, kto robi konteneryzację?

Dlaczego więc tak bardzo zależy nam na funkcji input to map/flatMap czy konteneryzacji do M[B] czy na samej mapie czy konteneryzacji dla nas?

Ty zobacz w kontekście for comprehension to, co się dzieje, to wielokrotne przekształcenia na elemencie dostarczonym w for, więc dajemy kolejnemu pracownikowi na naszej linii produkcyjnej możliwość określenia opakowania. wyobraź sobie, że mamy linię montażową każdy pracownik robi coś z produktem i tylko ostatni pracownik pakuje go w Pojemnik! Witamy w flatMap to jest jego cel, w map każdy pracownik po zakończeniu pracy nad przedmiotem pakuje go, więc dostajesz kontenery nad kontenery.

Potężni dla zrozumienia

Przyjrzyjmy się teraz twojemu zrozumieniu, biorąc pod uwagę to, co powiedzieliśmy powyżej:]}
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)   
    g <- mkMatcher(pat2)
} yield f(s) && g(s)

Co my tu mamy:

  1. mkMatcher zwraca container kontener zawiera funkcję: String => Boolean
  2. reguły są jeśli mamy wiele <- tłumaczą się na flatMap z wyjątkiem ostatniego.
  3. ponieważ f <- mkMatcher(pat) jest pierwszy w sequence (pomyśl assembly line) wszystko, czego chcemy, to wziąć f i przekazać go do następnego pracownika na linii montażowej, pozwalamy następnego pracownika na naszej linii montażowej (następna funkcja) możliwość określenia, co będzie opakowanie z powrotem naszego przedmiotu, dlatego ostatnią funkcją jest map.
  4. Ostatni g <- mkMatcher(pat2) użyje map to dlatego, że jego ostatni w linii montażowej! więc może po prostu wykonać ostatnią operację z map( g =>, która tak! wyciąga g i używa f, który został już wyciągnięty z pojemnika przez flatMap dlatego koniec z pierwszym:

    MkMatcher(pat) flatMap (f // pull out f function give ittem 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)...)) / / ponieważ jest to ostatnia funkcja na linii montażowej użyjemy map i wyciągniemy g z pojemnika i do opakowania z powrotem, jego map i to opakowanie będzie Dławić / align = "left" /

 2
Author: Tomer Ben David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-25 16:17:04

Można to przetłumaczyć jako:

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)

Uruchom to dla lepszego widoku, jak jego Rozszerzony

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)

Wyniki to:

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

Jest to podobne do flatMap - pętla przez każdy element w pat i foreach element map to do każdego elementu w pat2

 1
Author: korefn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-30 08:14:28

Po pierwsze, mkMatcher zwraca funkcję, której sygnatura to String => Boolean, jest to zwykła procedura java, która po prostu uruchamia Pattern.compile(string), Jak pokazano w funkcji pattern. Następnie spójrz na tę linię

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

Funkcja map jest stosowana do wyniku pattern, który jest Option[Pattern], więc p w p => xxx jest tylko wzorem, który skompilowałeś. Tak więc, biorąc pod uwagę wzór p, konstruowana jest nowa funkcja, która przyjmuje ciąg s i sprawdza, czy s pasuje do wzoru.

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

Uwaga, p zmienna jest ograniczona do skompilowanego wzorca. Teraz jest jasne, jak funkcja z podpisem String => Boolean jest skonstruowana przez mkMatcher.

Następnie sprawdźmy funkcję bothMatch, która jest oparta na mkMatcher. Aby pokazać, jak działa bothMathch, najpierw przyjrzymy się tej części:

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

Ponieważ otrzymaliśmy funkcję z podpisem String => Boolean z mkMatcher, która jest g w tym kontekście, g(s) jest równoważna Pattern.compile(pat2).macher(s).matches, która zwraca, jeśli łańcuch s pasuje do wzorca pat2. Więc może f(s), to samo co g(s), jedyną różnicą jest to, że pierwsze wywołanie mkMatcher używa flatMap zamiast map, Dlaczego? Ponieważ mkMatcher(pat2) map (g => ....) zwraca Option[Boolean], otrzymasz zagnieżdżony wynik Option[Option[Boolean]] jeśli użyjesz map dla obu wywołań, nie tego chcesz .

 0
Author: xiaowl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-30 08:14:11