【问题标题】:Convert a List of Options to an Option of List using Scalaz使用 Scalaz 将选项列表转换为列表选项
【发布时间】:2011-02-03 20:52:59
【问题描述】:

我想将List[Option[T]] 转换为Option[List[T]]。函数的签名类型是

def lo2ol[T](lo: List[Option[T]]): Option[List[T]]

预期的行为是将仅包含Somes 的列表映射到包含元素Some 中的元素列表的Some。另一方面,如果输入列表至少有一个None,则预期的行为是只返回None。例如:

scala> lo2ol(Some(1) :: Some(2) :: Nil)
res10: Option[List[Int]] = Some(List(1, 2))

scala> lo2ol(Some(1) :: None :: Some(2) :: Nil)
res11: Option[List[Int]] = None

scala> lo2ol(Nil : List[Option[Int]])
res12: Option[List[Int]] = Some(List())

没有 scalaz 的示例实现是:

def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
  lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => (o, ol) match {
    case (Some(x), Some(xs)) => Some(x :: xs);
    case _ => None : Option[List[T]]; 
}}}

我记得在某处看到过类似的示例,但使用 Scalaz 来简化代码。会是什么样子?


稍微简洁一点的版本,使用 Scala2.8 PartialFunction.condOpt,但仍然没有 Scalaz:

import PartialFunction._

def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
  lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => condOpt(o, ol) {
    case (Some(x), Some(xs)) => x :: xs
  }
}}

【问题讨论】:

    标签: scala option scalaz


    【解决方案1】:

    Scalaz 中有一个函数可以将List[Option[A]] 转换为Option[List[A]]。这是sequence。要在任何元素为 None 的情况下获取 None,在所有元素为 Some 的情况下获取 Some[List[A]],您可以这样做:

    import scalaz.syntax.traverse._
    import scalaz.std.list._     
    import scalaz.std.option._
    
    lo.sequence
    

    鉴于存在Traverse[F]Applicative[G] 的实现(OptionList 恰好满足这两者并且由这些导入提供),此方法实际上将F[G[A] 转换为G[F[A]]

    Applicative[Option] 的语义是这样的,如果Options 的List 的任何元素是None,那么sequence 也将是None。如果您想获取所有Some 值的列表,而不管其他值是否为None,您可以这样做:

    lo flatMap (_.toList)
    

    您可以将其概括为任何Monad 也形成MonoidList 恰好是其中之一):

    import scalaz.syntax.monad._
    
    def somes[F[_],A](x: F[Option[A]])
                     (implicit m: Monad[F], z: Monoid[F[A]]) =
      x flatMap (o => o.fold(_.pure[F])(z.zero))
    

    【讨论】:

    • 但如果您只需要List[Option[A]]Some 值,则不再需要Option。您将有一个空列表或A 的非空列表。
    • 实际上,我确实想得到None,以防任何元素是None,所以sequence正是我所要求的。我将尝试编辑问题以阐明要求。
    • 我无法让lo.sequence 工作。使用 scala-2.8.0.Beta1 和 scalaz-core_2.8.0.Beta1-5.0.1-SNAPSHOT.jar,如果我输入def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = lo.sequence,我会得到<console>:10: error: diverging implicit expansion for type scalaz.Applicative[N]
    • import scalaz._; import Scalaz._; List(some(1), some(2), some(3)).sequence。我得到:Some(List(1,2,3))
    • 赛斯:这很奇怪。如果你省略返回类型,它工作得很好。 def lo2ol[T](lo: List[Option[T]]) = lo.sequence
    【解决方案2】:

    由于某些原因你不喜欢

    if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
    

    ?这可能是没有 Scalaz 的 Scala 中最短的。

    【讨论】:

    • 不错,但它超过了列表两次。
    • @Apocalisp:确实如此,但这几乎总是比制作新列表的一半然后将其全部丢弃并扔回None要便宜。
    • if (lo contains None) None else Some(lo.flatten) 会不会更短?
    • @ErikAllik - 是的,稍微有点,不过我会避免使用flatten,因为您在此过程中会将Option 转换为List。不过,请致电contains
    • @RexKerr:我懒得继续思考,但我知道flatten 有一些不太理想的地方。
    【解决方案3】:

    Scala 2.13 开始,并将Option::unless 构建器添加到标准库Rex Kerr's answer 的变体将是:

    Option.unless(list contains None)(list.flatten)
    // val list = List(Some(1), Some(2))          =>    Some(List(1, 2))
    // val list = List(Some(1), None, Some(2))    =>    None
    

    或者,如果性能受到威胁(为了避免 flattenOptionList 的隐式转换):

    Option.unless(list contains None)(list.map(_.get))
    

    【讨论】:

      【解决方案4】:

      虽然 Scalaz 中的 Applicative[Option] 具有直接使用 MA#sequence 的错误行为,但您也可以从 Monoid 派生 Applicative。使用MA#foldMapDefaultMA#collapse 可以很方便。

      在这种情况下,我们使用Monoid[Option[List[Int]]。我们首先执行一个内部映射 (MA#∘∘) 以将单个 Ints 包装在一个元素的 Lists 中。

      (List(some(1), none[Int], some(2)) ∘∘ {(i: Int) => List(i)}).collapse assert_≟ some(List(1, 2))
      (List(none[Int]) ∘∘ {(i: Int) => List(i)}).collapse                   assert_≟ none[List[Int]]
      (List[Option[Int]]() ∘∘ {(i: Int) => List(i)}).collapse               assert_≟ none[List[Int]]
      

      List 抽象到具有TraversePointedMonoid 实例的任何容器:

      def co2oc[C[_], A](cs: C[Option[A]])
                        (implicit ct: Traverse[C], cp: Pointed[C], cam: Monoid[C[A]]): Option[C[A]] =
        (cs ∘∘ {(_: A).pure[C]}).collapse
      
      
      co2oc(List(some(1), none[Int], some(2)))   assert_≟ some(List(1, 2))
      co2oc(Stream(some(1), none[Int], some(2))) assert_≟ some(Stream(1, 2))
      co2oc(List(none[Int]))                     assert_≟ none[List[Int]]
      co2oc(List[Option[Int]]())                 assert_≟ none[List[Int]]
      

      遗憾的是,目前尝试编译此代码要么触发#2741,要么将编译器送入无限循环。

      更新 为了避免遍历列表两次,我应该使用foldMapDefault

      (List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
      

      此答案基于原始请求,即空列表或仅包含 Nones 的列表应返回 None。顺便说一句,这最好用Option[scalaz.NonEmptyList] 类型建模——NonEmptyList 保证至少有一个元素。

      如果您只想要List[Int],有许多更简单的方法,在其他答案中给出。没有提到的两种直接方式:

      list collect { case Some(x) => x }
      list flatten
      

      【讨论】:

      • Apocalisp 的答案中的sequence 方法完美地满足了这个问题背后的真正用例,包括给出空列表时的结果(可能在所述用例中发生)。不过感谢提到 NEL 类型,它看起来很方便。
      • @retronym:Rafael 写道“如果输入列表至少有一个 None,那么预期的行为就是返回 None”。当你写“一个只包含无的列表,应该返回一个无”的地方。那是非常不同的。 “列表展平”并没有满足 Rafael 的实际要求。
      • 我的错,我误解了这个问题。 MA#foldMapDefaultMA#sequence 都使用 Traverse[List],但使用不同的 Applicative Functor。
      【解决方案5】:

      这对我有用。我希望这是一个正确的解决方案。

      如果 List 中的 Options 之一为 None,则返回 None,否则返回 List[A] 的 Option

      def sequence[A](a: List[Option[A]]): Option[List[A]] = {
      
        a.foldLeft(Option(List[A]())) {
          (prev, cur) => {
      
            for {
              p <- prev if prev != None
              x <- cur
            } yield x :: p
      
          }
        }
      
      }
      

      【讨论】:

        猜你喜欢
        • 2021-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-06
        • 2013-01-11
        • 2013-11-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多