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.
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]
gdzieM[_]: Option[_]
A: Int
-
Future[String => Boolean]
gdzieM[_]: 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
-
Każda linia w wyrażeniu używająca symbolu
<-
jest tłumaczona na wywołanieflatMap
, z wyjątkiem ostatniej linii, która jest tłumaczona na wywołanie końcowemap
, gdzie "związany symbol" po lewej stronie jest przekazywany jako parametr do funkcji argumentu (co wcześniej nazywaliśmyf: 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
-
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]
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. MetodamkMatcher
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 map
ponownie 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
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
- zrozumieć scalę
map
. - zrozumieć scalę
flatMap
. - 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:
-
map
wybiera kontener wyjściowy dla nas i będzie on tym samym kontenerem co źródło, nad którym pracujemy, więc dlaM[A]
kontenera otrzymujemy ten samM
kontener tylko dlaB
M[B]
i nic więcej! -
map
czy ta konteneryzacja dla nas wystarczy dać mapowanie zA
doB
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:
-
mkMatcher
zwracacontainer
kontener zawiera funkcję:String => Boolean
- reguły są jeśli mamy wiele
<-
tłumaczą się naflatMap
z wyjątkiem ostatniego. - ponieważ
f <- mkMatcher(pat)
jest pierwszy wsequence
(pomyślassembly 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ą jestmap
. -
Ostatni
g <- mkMatcher(pat2)
użyjemap
to dlatego, że jego ostatni w linii montażowej! więc może po prostu wykonać ostatnią operację zmap( g =>
, która tak! wyciągag
i używaf
, który został już wyciągnięty z pojemnika przezflatMap
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, jegomap
i to opakowanie będzie Dławić / align = "left" /
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
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 .
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