Почему Scala выбирает тип «Product» для «for» выражений, включающих определения Either и value - PullRequest
14 голосов
/ 03 сентября 2011

Если я создаю для понимания с определением значения с помощью Option, он работает, как и ожидалось:

scala> for (a <- Some(4); b <- Some(5); val p = a * b) yield p
res0: Option[Int] = Some(20)

То же самое с любым из них работает, если у меня нет определения значения:

scala> for (a <- Right(4).right; b <- Right(5).right) yield a * b
res1: Either[Nothing,Int] = Right(20)

Но если я использовал определение значения, похоже, что scala выводит неверный тип контейнера для понимания:

scala> for (a <- Right(4).right; b <- Right(5).right; val p = a * b) yield p
<console>:8: error: value map is not a member of Product with Serializable with Either[Nothing,(Int, Int)]
for (a <- Right(4).right; b <- Right(5).right; val p = a * b) yield p
                            ^

Почему это происходит?Какие способы обойти это поведение доступны?

1 Ответ

19 голосов
/ 03 сентября 2011

Проблема исходит от val p = a*b Если пишешь проще

для (a <- справа (4). Right; b <- справа (5) .right) a * b </p>

он компилируется и вы получите правильный результат.

У вашей проблемы две причины

Во-первых, Either проекции map и flatMap не имеют обычной подписи, а именно для карт процедур и flatMap, определенных в универсальном классе M[A], (A => B) => M[B] и (A => M[B]) => M[B]. M[A] подпрограмма определена в Either[A,B].RightProjection, но в результатах и ​​аргументах мы имеем Either[A,B], а не проекцию.

Во-вторых, переводится val p = a*b в понимании. Scala Reference, 6,19 стр. 90:

Генератор p <- e, за которым следует определение значения p ′ ​​= e ′, переводится в следующий генератор пар значений, где х и x ′ - свежие имена: </p>

(p,p′) <- for(x@p<-e) yield {val x′@p′ = e′; (x,x′)}

Давайте немного упростим код, отбросив a <-. Кроме того, b и p переименованы в p и pp, чтобы быть ближе к правилу перезаписи, с pp для p'. a должен быть в области видимости для (p <- Right (5) .right; val pp = a * p) доходность pp </p>

следуя правилу, мы должны заменить генератор + определение. Что вокруг этого, for( и )yield pp, без изменений.

for((p, pp) <- for(x@p <- Right(5).right) yield{val xx@pp = a*p; (x,xx)}) yield pp

Внутренний элемент for переписан на простую карту

for((p, pp) <- Right(5).right.map{case x@p => val xx@pp = a*p; (x,xx)}) yield pp

Вот проблема. Right(5).right.map(...) имеет тип Either[Nothing, (Int,Int)], а не Either.RightProjection[Nothing, (Int,Int)], как хотелось бы. Он не работает во внешнем формате for (который также преобразуется в map. Для Either нет метода map, он определен только для проекций.

Если вы внимательно посмотрите на ваше сообщение об ошибке, оно говорит об этом, даже если в нем упоминаются Product и Serializable, оно говорит, что это Either[Nothing, (Int, Int)] и что на нем не определена карта. Пара (Int, Int) происходит непосредственно из правила перезаписи.

Понимание предназначено для правильной работы при соблюдении правильной подписи. С помощью трюка с Either проекциями (что тоже имеет свои преимущества) мы получаем эту проблему.

...