Converta uma lista de opções em uma opção de lista usando Scalraz
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 Some
s 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
}
}}
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 Option
s 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 Int
pecado List
s 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 None
s, 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 flatten
está implícita de conversão de Option
para List
):
Option.unless(list contains None)(list.map(_.get))