Pergunta

Eu quero transformar um List[Option[T]] dentro de Option[List[T]]. O tipo de assinatura da função é

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

O comportamento esperado é mapear uma lista que contém apenas Somes em um Some contendo uma lista dos elementos dentro dos elementos Some's. Por outro lado, se a lista de entrada tiver pelo menos um None, o comportamento esperado é apenas retornar None. Por exemplo:

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

Um exemplo de implementação, sem Scalaz, seria:

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]]; 
}}}

Lembro -me de ter visto em algum lugar um exemplo semelhante, mas usando o Scalaz para simplificar o código. Como seria?


Uma versão um pouco mais sucinta, usando o Scala2.8 PartialFunction.condOpt, mas ainda sem 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
  }
}}
Foi útil?

Solução

Há uma função que transforma um List[Option[A]] em um Option[List[A]] em Scalaz. Isso é sequence. Para obter None Caso algum dos elementos seja None e a Some[List[A]] Caso todos os elementos sejam Some, você pode simplesmente fazer isso:

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

lo.sequence

Este método realmente gira F[G[A] em G[F[A]] dado que existe uma implementação de Traverse[F], e de Applicative[G] (Option e List por acaso satisfazer os dois e são fornecidos por essas importações).

A semântica de Applicative[Option] são tais que se algum dos elementos de um List do Options são None, então o sequence vai ser None também. Se você quiser obter uma lista de todos os Some valores, independentemente de serem outros valores None, você consegue fazer isso:

lo flatMap (_.toList)

Você pode generalizar isso para qualquer Monad que também forma um Monoid (List acontece que é um desses):

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

Outras dicas

Por algum motivo que você não gosta

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

? Esse é provavelmente o mais curto de Scala sem Scalaz.

Enquanto o Applicative[Option] em Scalaz tem o comportamento errado para usar diretamente MA#sequence, você também pode derivar um Applicative a partir de um Monoid. Isso é tornado conveniente com MA#foldMapDefault ou MA#collapse.

Nesse caso, usamos um Monoid[Option[List[Int]]. Primeiro realizamos um mapa interno (MA#∘∘) para envolver o indivíduo Intpecado Lists de um elemento.

(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]]

Abstrair de List para qualquer contêiner com instâncias para Traverse, Pointed e Monoid:

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]]

Infelizmente, tentando compilar este código atualmente, ambos gatilhos #2741 ou envia o compilador para um loop infinito.

ATUALIZARPara evitar atravessar a lista duas vezes, eu deveria ter usado foldMapDefault:

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

Esta resposta foi baseada no pedido original de que uma lista vazia ou uma lista que contém apenas Nones, deve devolver um None. Aliás, isso seria melhor modelado pelo tipo Option[scalaz.NonEmptyList] -- NonEmptyList garante pelo menos um elemento.

Se você só quer o A List[Int], existem muitas maneiras mais fáceis, dadas de outras respostas. Duas maneiras diretas que não foram mencionadas:

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

Isso funcionou para mim. Espero que seja uma solução correta.

Ele retorna nenhum se uma das opções da lista não for nenhuma, caso contrário, retorna a opção de lista [a

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

    }
  }

}

Iniciando Scala 2.13, e a adição do Option::unless construtor para o Biblioteca padrão, uma variante para Resposta de Rex Kerr seria:

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

ou, se o desempenho estiver em jogo (a fim de evitar flattenestá implícita de conversão de Option para List):

Option.unless(list contains None)(list.map(_.get))
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top