【问题标题】:How can you recursively construct a list inside of a singular Some trait in Scala?在 Scala 中,如何递归地在单个 Some trait 中构造一个列表?
【发布时间】:2018-08-26 05:32:42
【问题描述】:

我现在正在阅读红书“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 类型的列表。但我不完全确定是否就在这里。

如果有什么不同,我将使用带有 :paste 命令的 scala 解释器。我不确定这是否会让事情变得更加糟糕。

【问题讨论】:

  • traverse 返回您,例如Some(list),您想将您的 hh 添加到该列表,而不是 Some。您通常使用map: Some(list).map(hh::_) 执行此操作,这将返回您Some(hh::list)
  • 为什么我没有想到!...我至少很接近。

标签: scala error-handling functional-programming


【解决方案1】:

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

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

Some 特质

Some 不是特征,而是class extending Option

它返回一个包含在 Some 中的 List。

它返回一个包含在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-comprehension:

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)
}

【讨论】:

  • 我的逻辑至少是正确的吗?那是因为函数的类型签名是 Option[List[A]] 它不会编译?这就是为什么需要使用映射才能真正匹配类型。另外,我对 flatMap 的看法是否正确?
  • 你认为我在这里提问是在作弊吗?我认为这本书的作者打算让你努力,这样你就可以自己培养对事物的直觉......(我假设你读过这本书)
  • @DanielLockHart 你只是错过了map 从不正确的hh :: traverse(t)(f)traverse(t)(f) map (hh :: _),其余的似乎或多或少是正确的。不,我不认为这是“作弊”——读完这本书后,我开始看到到处都是 applicatives 和 traversables,所以我想通过使用这些技术实际解决我当前的问题,我得到了足够的锻炼(诚然,当我阅读书,这些想法对我来说并不新鲜,所以我不想解决所有的练习)。
  • @DanielLockHart 如果您想进行“替换练习”:在不使用flatMap 的情况下重做相同的练习,请改用zip
【解决方案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得到的是一个里面有list的容器,你需要申请map。而且因为如果发生“失败”应该被传播,你需要flatMap

【讨论】:

  • @AndreyTyukin 谢谢,已修复。
  • 也许应该注意,Option 是一个 monad 的事实在这里并不真正需要:Option.applyzipmap 就足够了,因为它足以成为 @ 987654342@为了能够遍历traversables,flatMap其实是大材小用了。
猜你喜欢
  • 2011-04-08
  • 2019-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-14
  • 1970-01-01
  • 2012-05-04
相关资源
最近更新 更多