나는 정말로 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
이해가 결합 구문 바로 가기입니다 flatMap
및 map
읽기에 대한 이유 쉽다 방법이다.
일을 조금 단순화하고 class
앞서 언급 한 두 가지 방법을 모두 제공 하는 모든 것이 a라고 할 수 있다고 가정 하고 내부 유형이있는 a를 의미하는 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)
표현을 위해
-
<-
기호를 사용하는 표현식의 각 행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
-
하나만있는 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
이해를 통해 유지되므로 List
in으로 시작 company.branches
하고 List
.
대신 내부 유형이 변경되고 다음 yield
표현식에 의해 결정됩니다.customer.value: Int
valueList
이어야합니다 List[Int]
답변
나는 스칼라 메가 마인드가 아니기 때문에 자유롭게 나를 바로 잡으십시오. 그러나 이것이 내가 flatMap/map/for-comprehension
사가를 나 자신에게 설명하는 방법입니다 !
이해하기 for comprehension
과에 그것의 번역 scala's map / flatMap
우리는 작은 조치를 취할과 구성 부품을 이해해야한다 – map
와 flatMap
. 그러나없는 scala's flatMap
단지 map
로 flatten
당신이 너 자신을 물어! 그렇다면 왜 그렇게 많은 개발자들이 그것 또는 for-comprehension / flatMap / map
. 음, 스칼라 map
와 flatMap
시그니처를 살펴보면 동일한 반환 유형을 반환 M[B]
하고 동일한 입력 인수 A
(적어도 사용하는 함수의 첫 번째 부분)에서 작동하는 것을 볼 수 있습니다. 그렇다면 무엇이 차이가나요?
우리의 계획
- 스칼라의
map
. - 스칼라의
flatMap
. - 스칼라의
for comprehension
.` 이해
스칼라의지도
스칼라 맵 서명 :
map[B](f: (A) => B): M[B]
그러나 우리가이 서명을 볼 때 누락 된 큰 부분이 있습니다. 이것은 어디 A
에서 오는 것일까 요? 컨테이너는 유형 A
이므로 컨테이너 의 컨텍스트에서이 함수를 보는 것이 중요합니다 M[A]
. 우리의 컨테이너가 될 수있는 List
유형의 항목을 A
우리의 map
기능은 유형의 각 항목을 변환하는 기능 걸리는 A
타입을 B
, 다음 유형의 컨테이너를 반환 B
(또는 M[B]
)
컨테이너를 고려하여 맵의 서명을 작성해 보겠습니다.
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]
맵에 대한 매우 중요한 사실에 유의하십시오. 맵 은 제어 할 수없는 출력 컨테이너에 자동으로 번들 됩니다M[B]
. 다시 강조하겠습니다.
map
우리를 위해 출력 컨테이너를 선택하고 우리가 작업하는 소스와 동일한 컨테이너가 될 것이므로 컨테이너에 대해서만M[A]
동일한M
컨테이너를 얻습니다B
M[B]
.map
우리는 단지에서 매핑을 우리를 위해이 컨테이너 수송을 주는가A
에B
그리고의 상자에 넣어 것입니다M[B]
우리를 위해 상자에 넣어 것입니다!
containerize
내부 항목을 변환하는 방법을 방금 지정한 항목에 지정하지 않은 것을 알 수 있습니다. 그리고 우리는 M
둘 다에 대해 동일한 컨테이너 를 가지고 M[A]
있고 M[B]
이것은 M[B]
동일한 컨테이너라는 것을 의미합니다. 즉, 만약 당신이 가지고 있다면 당신 List[A]
은 a를 가질 List[B]
것이고 더 중요한 map
것은 당신을 위해 그것을하는 것입니다!
이제 우리는 처리했는지 map
에에하자 이동 flatMap
.
스칼라의 flatMap
그 서명을 보자 :
flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]
map flatMap
에서 flatMap으로 변환 할 A to B
뿐만 아니라 컨테이너화하는 함수를 제공하는 flatMap 의 큰 차이를 볼 수 있습니다 M[B]
.
컨테이너화를 수행하는 사람이 누구인지 왜 우리가 신경 쓰나요?
그렇다면 왜 우리는 map / flatMap에 대한 입력 함수에 신경을 많이 써서 컨테이너화를 수행 M[B]
하거나지도 자체가 컨테이너화를 수행 합니까?
for comprehension
무슨 일이 일어나고 있는지의 맥락에서에서 제공되는 항목에 대한 여러 변형이 for
있으므로 조립 라인의 다음 작업자에게 포장을 결정할 수있는 기능을 제공합니다. 각 작업자가 제품에 대해 작업을 수행하고 마지막 작업자 만 컨테이너에 포장하는 조립 라인이 있다고 상상해보십시오! 이것에 오신 것을 환영합니다 flatMap
. map
각 작업자가 항목 작업을 마쳤을 때 컨테이너를 통해 컨테이너를 얻을 수 있도록 포장합니다.
이해력이 강하다
이제 위에서 말한 것을 고려하여 이해를 위해 살펴 보겠습니다.
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
f <- mkMatcher(pat)
g <- mkMatcher(pat2)
} yield f(s) && g(s)
우리가 여기에있는 것 :
mkMatcher
container
컨테이너에 함수가 포함 된 것을 반환 합니다.String => Boolean
- 규칙은 마지막 것을 제외하고 여러
<-
개로 변환되는flatMap
경우입니다. - 대로
f <- mkMatcher(pat)
에서 처음으로sequence
(생각하는assembly line
우리가 걸릴 것입니다 밖으로 원하는 모든)f
과 조립 라인에서 다음 근로자에 전달, 우리는 우리의 조립 라인에서 다음 노동자 (다음 기능)이 될 것입니다 결정하는 능력을하자 우리 항목의 뒷면을 포장하는 것이 마지막 기능인 이유입니다map
. -
마지막으로
g <- mkMatcher(pat2)
사용map
이 조립 라인에서 때문에 마지막! 그래서 그것은 마지막 작업을 할 수 있습니다map( g =>
. 컨테이너에서 이미 꺼낸를 꺼내g
사용 하므로 먼저 다음과 같이 끝납니다.f
flatMap
mkMatcher (pat) flatMap (f // pull out f function give item to next assembly line worker (you see it have access to
f
, and not package it back i mean let the map to determine the packaging let the next assembly line worker container. mkMatcher (pat2) map (g => f (s) …)) // 이것이 어셈블리 라인의 마지막 함수이므로 map을 사용하고 컨테이너에서 g를 다시 패키징으로 당깁니다. , 그것map
과이 포장은 끝까지 조절되어 우리의 포장이나 용기가 될 것입니다, yah!
답변
그 이유는 이점으로 적절한 “빠른 실패”오류 처리를 제공하는 모나드 연산을 연결하는 것입니다.
사실 꽤 간단합니다. mkMatcher
방법은 리턴 Option
(모나드이다). mkMatcher
모나드 연산 의 결과는 a None
또는 a Some(x)
입니다.
적용 map
또는 flatMap
A와 기능을 None
항상 것은 반환 None
– 함수는 매개 변수로 전달 map
하고 flatMap
평가되지 않습니다.
따라서 귀하의 예제에서 mkMatcher(pat)
None을 반환하면 적용된 flatMap은 a를 반환하고 None
(두 번째 모나드 연산 mkMatcher(pat2)
은 실행되지 않음) 최종 결과 map
는 다시 None
. 즉, for comprehension의 작업 중 하나라도 None을 반환하면 Fail Fast 동작이 발생하고 나머지 작업은 실행되지 않습니다.
이것은 오류 처리의 모나 딕 스타일입니다. 명령형 스타일은 기본적으로 점프 (catch 절로) 인 예외를 사용합니다.
마지막 참고 :이 patterns
함수는 명령형 오류 처리 ( try
… catch
)를 다음을 사용하여 모나 딕 스타일 오류 처리 로 “변환”하는 일반적인 방법입니다.Option
답변
이것은 다음과 같이 traslated 될 수 있습니다.
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)
확장 방법을 더 잘 보려면 이것을 실행하십시오.
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)
결과는 다음과 같습니다.
1 -> a
1 -> b
1 -> c
...
2 -> a
2 -> b
...
이것은 다음과 유사합니다 flatMap
.-각 요소를 통해 루프 pat
하고 foreach 요소 map
를 각 요소에 대해pat2
답변
먼저 mkMatcher
시그니처가 String => Boolean
인 함수를 반환합니다. 이는 함수에 Pattern.compile(string)
표시된대로 방금 실행되는 일반 Java 프로 시저입니다 pattern
. 그럼이 줄을 봐
pattern(pat) map (p => (s:String) => p.matcher(s).matches)
map
기능의 결과에 적용 pattern
하고, Option[Pattern]
소위, p
에가 p => xxx
당신이 컴파일 된 바로 패턴이다. 따라서 pattern이 주어지면 p
String을 취하고 패턴과 일치 s
하는지 확인 하는 새 함수가 생성 s
됩니다.
(s: String) => p.matcher(s).matches
참고는 p
변수는 컴파일 된 패턴에 묶여있다. 이제 시그니처 String => Boolean
가 있는 함수 가 mkMatcher
.
다음 bothMatch
으로 mkMatcher
. bothMathch
작동 방식 을 보여주기 위해 먼저이 부분을 살펴 봅니다.
mkMatcher(pat2) map (g => f(s) && g(s))
우리가 서명 함수 도착 이후 String => Boolean
부터 mkMatcher
이며, g
이러한 맥락에서를,g(s)
하는 것과 같습니다 Pattern.compile(pat2).macher(s).matches
, 어떤 반환 문자열의 일치 패턴 경우 pat2
. 그래서 어떻게 f(s)
, 그것은 g(s)
,, 유일한 차이점은 , 왜 대신에 mkMatcher
uses 의 첫 번째 호출이라는 flatMap
것입니다 map
. mkMatcher(pat2) map (g => ....)
returns 이므로 두 호출 모두에 사용하면 Option[Boolean]
중첩 된 결과를 얻을 Option[Option[Boolean]]
수 있습니다 map
.