当参数不期望尝试时,为什么Try在这里工作?

时间:2015-09-05 00:33:55

标签: scala

我正在阅读The Neophyte's Guide to Scala Part 8: Welcome to the Future,我感到困惑的一部分代码是:

import scala.util.Try

object CoffeeSync extends App {

  // Some type aliases, just for getting more meaningful method signatures:
  type CoffeeBeans = String
  type GroundCoffee = String
  case class Water(temperature: Int)
  type Milk = String
  type FrothedMilk = String
  type Espresso = String
  type Cappuccino = String

  // dummy implementations of the individual steps:
  def grind(beans: CoffeeBeans): GroundCoffee = s"ground coffee of $beans"
  def heatWater(water: Water): Water = water.copy(temperature = 85)
  def frothMilk(milk: Milk): FrothedMilk = s"frothed $milk"
  def brew(coffee: GroundCoffee, heatedWater: Water): Espresso = "espresso"
  def combine(espresso: Espresso, frothedMilk: FrothedMilk): Cappuccino = "cappuccino"

  // going through these steps sequentially:
  def prepareCappuccino(): Try[Cappuccino] = for {
    ground <- Try(grind("arabica beans"))
    water <- Try(heatWater(Water(25)))
    espresso <- Try(brew(ground, water))
    foam <- Try(frothMilk("milk"))
  } yield {
    combine(espresso, foam)
  }

所有这一切都很好,但我感到困惑的是为什么Try在For Fomprehension中起作用?

grind("arabica beans")应返回GroundCoffee类型的值,该类型是String的类型别名。我知道For Comprehensions正在迭代一个Collection并且这被分配给一个值,并且Try(grind(...))被视为1个元素的集合,因此ground代表元素&#34; unwrapped&# 34。

但如果解释完成,那么当我执行以下操作时,我不会遇到编译错误:

// going through these steps sequentially:
  def prepareCappuccino(): Try[Cappuccino] = for {
    // Replaced Try with Seq
    ground <- Seq(grind("arabica beans"))
    water <- Try(heatWater(Water(25)))
    espresso <- Try(brew(ground, water))
    foam <- Try(frothMilk("milk"))
  } yield {
    combine(espresso, foam)
  }

这给了我以下内容:

    <console>:41: error: type mismatch;
 found   : scala.util.Try[CoffeeSync.Cappuccino]
    (which expands to)  scala.util.Try[String]
 required: scala.collection.GenTraversableOnce[?]
           water <- Try(heatWater(Water(25)))
                 ^
<console>:40: error: type mismatch;
 found   : Seq[Nothing]
 required: scala.util.Try[CoffeeSync.Cappuccino]
    (which expands to)  scala.util.Try[String]
           ground <- Seq(grind("arabica beans"))
                  ^

所以我想我真正要问的是为什么我的Seq[GroundCoffee]会转换为Seq[Nothing]感谢您的帮助。

1 个答案:

答案 0 :(得分:4)

  

我明白了For Comprehensions正在迭代一个集合

这不完全是事实。对于具有方法mapflatmapforeachfilterwithFilter的每种类型,可以使用理解。并且根据rules将每个for块隐式转换为这些方法调用的链。例如:

for (p1 <-e1; p2 <-e2) yield (p1, p2)

相当于:

e1.flatMap(p1 => e2.map(p2 => (p1, p2)))

在你的情况下(我简化了一点,这足以得到编译错误,无论你实际返回什么):

  def prepareCappuccino() = for {
    ground <- Seq(grind("arabica beans"))
    water <- Try(heatWater(Water(25)))
  } yield water

您的代码将被翻译为:

  def prepareCappuccino() = 
    Seq(grind("arabica beans"))
    .flatMap(ground => Try(heatWater(Water(25))).map(water => water))

这里有一个问题:flatMap中的方法Seq有签名:

def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That

需要f - 类型为A => GenTraversableOnce[B]的函数。但实际上你传递的是A => Try[B]类型的函数,因为Try[B]不是GenTraversableOnce[B]的子类型,所以你得到了编译错误。

因此,您无法以这种方式将TrySeq或其他集合合并到块中,除非您有从TryGenTraversableOnce的隐式转换。例如,您可以组合Option和集合:

val r = for {
  x <- Seq(3)
  y <- Option(5)
} yield (x,y)

此代码编译成功,因为Option对象中定义了隐式转换:

  implicit def option2Iterable[A](xo: Option[A]): Iterable[A] = xo.toList