이해를 위해 Scala의 유형 불일치


81

이 구조로 인해 Scala에서 유형 불일치 오류가 발생하는 이유는 무엇입니까?

for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

<console>:6: error: type mismatch;
 found   : List[(Int, Int)]
 required: Option[?]
       for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

Some을 List로 전환하면 잘 컴파일됩니다.

for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))

이것은 또한 잘 작동합니다.

for (first <- Some(1); second <- Some(2)) yield (first,second)

2
실패한 예제에서 Scala가 어떤 결과를 반환 할 것으로 예상 했습니까?
— Daniel C. Sobral 2011 년

1
내가 그것을 쓸 때 나는 Option [List [(Int, Int)]]를 얻을 것이라고 생각했습니다.
— Felipe Kamakura

답변:


117

이해를 위해 map또는 flatMap메서드 에 대한 호출로 변환됩니다 . 예를 들면 다음과 같습니다.

for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)

됩니다 :

List(1).flatMap(x => List(1,2,3).map(y => (x,y)))

따라서 첫 번째 루프 값 (이 경우 List(1))은 flatMap메서드 호출 을 수신합니다 . flatMapon a List가 다른 것을 반환 하기 때문에 Listfor comprehension의 결과는 물론 List. (이것은 나에게 새로운 것이 었습니다 Seq.

이제 다음에서 어떻게 flatMap선언 되는지 살펴보십시오 Option.

def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]

이것을 명심하십시오. 이해에 대한 오류 (가있는 오류 Some(1))가 일련의 맵 호출로 변환되는 방법을 살펴 보겠습니다 .

Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))

이제 flatMap호출 의 매개 변수가 필요에 따라 를 반환하지만는 반환 List하지 않는 것임을 쉽게 알 수 있습니다 Option.

문제를 해결하기 위해 다음을 수행 할 수 있습니다.

for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)

잘 컴파일됩니다. 종종 가정 하듯이 Option의 하위 유형이 아니라는 점은 주목할 가치가 Seq있습니다.


31

기억하기 쉬운 팁 은 이해 를 위해 첫 번째 생성기의 컬렉션 유형 인 Option [Int]를 반환하려고합니다. 따라서 Some (1)으로 시작 하면 Option [T]의 결과를 예상해야합니다.

목록 유형 의 결과를 원하면 목록 생성기로 시작해야합니다.

왜이 제한이 있고 항상 일종의 시퀀스를 원할 것이라고 가정하지 않습니까? 돌아 오는 것이 합리적 일 수 있습니다 Option. 다음 함수를 사용하여 Option[Int]를 얻기 위해 무언가와 결합하려는를 가질 수 있습니다 Option[List[Int]]. (i:Int) => if (i > 0) List.range(0, i) else None; 그런 다음 이것을 작성하고 "말이 안되는"경우 None을 얻을 수 있습니다.

val f = (i:Int) => if (i > 0) Some(List.range(0, i)) else None
for (i <- Some(5); j <- f(i)) yield j
// returns: Option[List[Int]] = Some(List(0, 1, 2, 3, 4))
for (i <- None; j <- f(i)) yield j
// returns: Option[List[Int]] = None
for (i <- Some(-3); j <- f(i)) yield j
// returns:  Option[List[Int]] = None

일반적인 경우에 대한 이해 를 확장 하는 방법 은 실제로 유형의 객체를 M[T]함수와 결합하여 유형 (T) => M[U]의 객체를 얻는 상당히 일반적인 메커니즘 M[U]입니다. 귀하의 예에서 M은 옵션 또는 목록이 될 수 있습니다. 일반적으로 동일한 유형이어야합니다 M. 따라서 Option과 List를 결합 할 수 없습니다. 다른 것들의 예를 들어 M, 이 특성의 하위 클래스를 보십시오 .

왜 결합 않은 List[T]으로 (T) => Option[T]당신이 목록 시작 때 비록 사용할 수 있습니까? 이 경우 라이브러리는 더 일반적인 유형을 사용합니다. 따라서 List와 Traversable을 결합 할 수 있으며 Option에서 Traversable 로의 암시 적 변환이 있습니다.

결론은 이것입니다. 표현식이 반환 할 유형을 생각하고 해당 유형을 첫 번째 생성자로 시작합니다. 필요한 경우 해당 유형으로 포장하십시오.


for이런 유형의 functor / monadic desugaring 을 정규 구문 으로 만드는 것은 나쁜 디자인 선택이라고 주장하고 싶습니다 . 같은 functor / monad 매핑에 대해 다른 이름의 메서드를 사용 하고 거의 다른 주류 프로그래밍 언어에서 오는 기대치와 일치하는 매우 간단한 동작을 갖도록 구문을 fmap예약 하지 않는 이유는 무엇 for입니까?
— ely

메인 스트림 순차 컴퓨팅 제어 흐름 문이 매우 놀랍고 미묘한 성능 문제 등을 가지지 않고도 원하는대로 별도의 fmap / lift 종류를 일반적인 것으로 만들 수 있습니다. "Everything works with for"는 그만한 가치가 없습니다.
— ely

4

Option이 Iterable이 아닌 것과 관련이있을 수 있습니다. 암시 Option.option2Iterable적은 컴파일러가 두 번째가 Iterable 일 것으로 예상하는 경우를 처리합니다. 루프 변수의 유형에 따라 컴파일러 마법이 다를 것으로 예상합니다.


1

나는 항상 이것이 도움이된다는 것을 알았다.

scala> val foo: Option[Seq[Int]] = Some(Seq(1, 2, 3, 4, 5))
foo: Option[Seq[Int]] = Some(List(1, 2, 3, 4, 5))

scala> foo.flatten
<console>:13: error: Cannot prove that Seq[Int] <:< Option[B].
   foo.flatten
       ^

scala> val bar: Seq[Seq[Int]] = Seq(Seq(1, 2, 3, 4, 5))
bar: Seq[Seq[Int]] = List(List(1, 2, 3, 4, 5))

scala> bar.flatten
res1: Seq[Int] = List(1, 2, 3, 4, 5)

scala> foo.toSeq.flatten
res2: Seq[Int] = List(1, 2, 3, 4, 5)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.