在 for comprehension 中切换 monad
Switching monad within a for comprehension
我正在尝试使用 for-comprehension 来处理 Option
s 的实例。但是,作为我的最后一步,我使用的函数不是 return 选项而是 List[Option[Something]]
(注意,不是 OptionT[List, Something]
)。如果列表中的所有选项都是 Some
的实例,我只想继续。有没有一种干净的方法可以将其作为 flatMap 调用的一部分进行处理?目前我是这样设置的:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2)
list <- if (rawList.forall(_.isDefined))
Some(rawList.map(_.get))
else
None
} { /* processing list */ }
如果有人问这个问题,我很抱歉,但我不知道我正在寻找的术语,所以我无法在这方面有效地搜索网络。
如果我遗漏了重要的理论要点,请提供有关事物我似乎难以理解的文献或其他资源的指针。
这是一种更简洁的测试方式:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2) if rawList.forall(_.isDefined)
list = rawList.flatten
} {/* processing list */}
或者您可以先创建扁平化列表,然后比较大小:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2)
list = rawList.flatten if list.size == rawList.size
} {/* processing list */}
如果 /* process list */
只是为了副作用而完成,即没有 yield
,最终目标是处理 list
的各个元素,从它们的 [=14] 中解包=]状态,那么你可能会做这样的事情。
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
lst = businessLogic3(o2)
if lst.forall(_.nonEmpty)
x <- lst.flatten
} {/* process x */}
简短的回答,你不能混用 Monad。请记住 for
只是 flatMap
的糖语法,它具有像 (F[A], A => F[B]) => F[B][=28= 这样的签名]
所以你必须在外面和里面有相同的 Monad。
另外,如果你有一个选项列表并且你想要一个列表选项,你可以使用 sequence
(from cats)
def processList(rawList: List[Foo]): List[Bar] = ???
val result: Option[List[Bar]] = for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList <- businessLogic3(o2).sequence
} yield processList(rawList)
如果您不使用 cats 并且不想包含它,您可以创建自己的 sequence
扩展方法 很容易只是一个选项列表。
implicit class ListOps[A](private val list: List[Option[A]]) extends AnyVal {
def sequence: Option[List[A]] = {
@annotation.tailrec
def loop(remaining: List[Option[A]], acc: List[A]): Option[List[A]] =
remaining match {
case Some(a) :: xs => loop(remaining = xs, a :: acc)
case None :: _ => None
case Nil => Some(acc.reverse)
}
loop(remaining = list, acc = List.empty)
}
}
我正在尝试使用 for-comprehension 来处理 Option
s 的实例。但是,作为我的最后一步,我使用的函数不是 return 选项而是 List[Option[Something]]
(注意,不是 OptionT[List, Something]
)。如果列表中的所有选项都是 Some
的实例,我只想继续。有没有一种干净的方法可以将其作为 flatMap 调用的一部分进行处理?目前我是这样设置的:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2)
list <- if (rawList.forall(_.isDefined))
Some(rawList.map(_.get))
else
None
} { /* processing list */ }
如果有人问这个问题,我很抱歉,但我不知道我正在寻找的术语,所以我无法在这方面有效地搜索网络。
如果我遗漏了重要的理论要点,请提供有关事物我似乎难以理解的文献或其他资源的指针。
这是一种更简洁的测试方式:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2) if rawList.forall(_.isDefined)
list = rawList.flatten
} {/* processing list */}
或者您可以先创建扁平化列表,然后比较大小:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2)
list = rawList.flatten if list.size == rawList.size
} {/* processing list */}
如果 /* process list */
只是为了副作用而完成,即没有 yield
,最终目标是处理 list
的各个元素,从它们的 [=14] 中解包=]状态,那么你可能会做这样的事情。
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
lst = businessLogic3(o2)
if lst.forall(_.nonEmpty)
x <- lst.flatten
} {/* process x */}
简短的回答,你不能混用 Monad。请记住 for
只是 flatMap
的糖语法,它具有像 (F[A], A => F[B]) => F[B][=28= 这样的签名]
所以你必须在外面和里面有相同的 Monad。
另外,如果你有一个选项列表并且你想要一个列表选项,你可以使用 sequence
(from cats)
def processList(rawList: List[Foo]): List[Bar] = ???
val result: Option[List[Bar]] = for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList <- businessLogic3(o2).sequence
} yield processList(rawList)
如果您不使用 cats 并且不想包含它,您可以创建自己的 sequence
扩展方法 很容易只是一个选项列表。
implicit class ListOps[A](private val list: List[Option[A]]) extends AnyVal {
def sequence: Option[List[A]] = {
@annotation.tailrec
def loop(remaining: List[Option[A]], acc: List[A]): Option[List[A]] =
remaining match {
case Some(a) :: xs => loop(remaining = xs, a :: acc)
case None :: _ => None
case Nil => Some(acc.reverse)
}
loop(remaining = list, acc = List.empty)
}
}