如何在Scala中以单数Some特质递归地构造列表?

时间:2018-08-25 20:09:20

标签: scala error-handling functional-programming

我现在正在阅读红皮书“ Scala中的功能编程”,因此同时我也在学习Scala。如果我理解正确,特质并不意味着对象。如果我错了,请纠正我。

我的问题是,我不知道如何将A类型的列表包装在Some特征内。我希望方向正确的提示。

在我做的练习中,要求我定义一个函数,该函数用于转换列表中的每个元素,然后将整个列表包含在Some特性内。

这是我的代码:

def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = a match {
    case Nil => Nil: B
    case h :: t => f(h) flatMap ( hh => hh :: traverse(t)(f))
  }

我觉得自己在正确的轨道上,但是scala解释器抱怨::无法使用Option[List[B]]。我认为这是因为函数的类型签名不返回列表,而是返回包装在Some中的List。

但是我对 flatMap 的直觉也可能是错误的吗? f(h)返回Option[B]。调用flatmap实际上看起来是在Option内部,所以hh是B类型的吗?我的逻辑是,通过这种方式,我可以使用函数hh :: traverse(t)(f)构造类型B的列表。但是我不确定是否会在这里。

如果有什么不同,我正在使用scala解释器和:paste命令。我不确定这是否会使事情变得不合时宜。

2 个答案:

答案 0 :(得分:3)

  

如果我理解正确,特质并不意味着对象。

特质是特质,物是物。特性有点像Java中的“接口”,object是单例对象。这些单例对象可以从特征扩展。

  

Some特征

Some不是特征,它是class extending Option

  

它返回包装在Some中的列表。

它返回一个包装在Option内的列表(即可以是None,根本没有任何列表)。为了在可选列表值上调用::,您需要另一个map

def traverse[A, B](a: List[A])(f: A => Option[B])
: Option[List[B]] = a match {
  case Nil => Some(Nil)
  case h :: t => f(h) flatMap {
    hValue => traverse(t)(f) map {
      tValue => hValue :: tValue
    }
  }
}

您可以缩写为:

def traverse[A, B](a: List[A])(f: A => Option[B])
: Option[List[B]] = a match {
  case Nil => Some(Nil)
  case h :: t => f(h) flatMap {
    hValue => traverse(t)(f) map (hValue :: _)
  }
}

或立即使用for理解:

def traverse[A, B](a: List[A])(f: A => Option[B])
: Option[List[B]] = a match {
  case Nil => Some(Nil)
  case h :: t => for {
    hValue <- f(h)
    tValue <- traverse(t)(f)
  } yield (hValue :: tValue)
}

答案 1 :(得分:2)

更多关于单子运算的信息。 Option是monad,但将其视为具有零个或一个值的容器可能会更容易。像List()一样没有元素,或者List(x)却只有一个元素。要修改隐藏在容器中的元素,您需要map

List(1).map(_ + 1) => List(2)
Some(1).map(_ + 1) => Some(2)

但是map无法更改列表中的元素数量。因此,您需要的是另一个名为flatMap的操作,它需要一个函数,该函数又要接受一个元素并返回一个List

List(1).flatMap { x => List() } => List()
Some(1).flatMap { x => None } => None

List(1).flatMap { x => List(x + 1) } => List(2)
Some(1).flatMap { x => Some(x + 1) } => Some(2)

这将传播“失败”,这是一个空列表,因为List().flatMap { ...whatever...}将始终返回一个空列表。

traverse就是这样做的,如果任何元素的函数f返回None(即我的解释是List()),则传播“失败”并合并非失败结果进入容器内的新列表。由于您从traverse获得的是一个内部包含列表的容器,因此您需要应用map。并且由于应该传播“故障”(如果发生),因此您需要flatMap