【问题标题】:Switching monad within a for comprehension在 for 理解中切换 monad
【发布时间】:2020-09-29 11:53:44
【问题描述】:

我正在尝试使用 for-comprehension 来处理 Options 的实例。但是,作为我的最后一步,我使用的函数不返回选项,而是返回 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 */ }

很抱歉,如果有人问了这个问题,但我不知道我正在寻找什么的术语,所以我无法在这方面有效地搜索网络。

如果我遗漏了一个重要的理论点,请提供关于我似乎难以理解的事物的文献或其他资源。

【问题讨论】:

标签: scala functional-programming monads


【解决方案1】:

这是一种更简洁的测试方式:

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 */}

【讨论】:

    【解决方案2】:

    如果/* process list */ 仅用于副作用,即没有yield,并且最终目标是处理list 的各个元素,从它们的Some(x) 状态解开,那么你可能会做类似的事情这个。

    for {
      o1 <- businessLogic1
      o2 <- businessLogic2(o1)
      lst = businessLogic3(o2)
      if lst.forall(_.nonEmpty)
      x <- lst.flatten
    } {/* process x */}
    

    【讨论】:

    • 如果我使用 yield 语句而不是常规的副作用,您会提出不同的建议吗?
    【解决方案3】:

    简短的回答,你不能混合 Monads。请记住,for 只是 flatMap 的糖语法,其签名类似于 (F[A], A => F[B]) => F[B]
    所以你必须在外部和内部都有相同的 Monad。

    另外,如果你有一个选项列表并且你想要一个列表选项,你可以使用sequence(来自猫)

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

    【讨论】:

    • 这是一个非常酷的解决方案,谢谢!我会问团队是否可以引入cats依赖。
    • @NiklasVest 啊,我以为你在使用 cats,因为你提到了OptionT。但是,仅针对 ListOptionsequence 的自定义实现非常简单。一会儿我会用它来编辑答案。
    • 我正在考虑开始使用它,因为无论如何我都在为我们的团队争取更多的 FP。巧合的是,我已经写了一个非常相似的“反转”类型的方法,虽然不像你在这里那样优雅。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2018-05-06
    • 2018-03-26
    • 1970-01-01
    • 2018-05-05
    • 1970-01-01
    • 2018-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多