【问题标题】:Converting a tuple of options to an option of tuple with Scalaz or Shapeless使用 Scalaz 或 Shapeless 将选项元组转换为元组选项
【发布时间】:2012-09-13 14:03:52
【问题描述】:

(Some(1), Some(2))

我希望得到

Some((1, 2))

并且拥有

(Some(1), None)

我希望得到

None

【问题讨论】:

  • 为什么不只是val (aOpt, bOpt) = optPair; aOpt.flatMap(a => bOpt.map(b => (a, b)))
  • @ErikAllik 这个问题是关于 Scalaz 的。

标签: scala scalaz shapeless


【解决方案1】:

我知道你在问 Scalaz,但值得指出的是,标准方法并不是令人难以忍受的罗嗦:

val x = (Some(1), Some(2))

for (a <- x._1; b <-x._2) yield (a,b)

在一般情况下(例如任意元组),Shapeless 最擅长这种事情。

【讨论】:

  • 当然。我觉得这将是唯一的解决方案,因为我无法想象他们如何在 Scalaz 中实现它,因为我看不到为元组的项目指定统一类型的方法,这似乎是该问题所必需的。只是出于好奇而问
  • @NikitaVolkov - 我几乎肯定你可以让 Shapeless 做到这一点,但我实际上没有机会使用它,所以我想不出一个例子。
  • @NikitaVolkov:你说得对,你不能为元组提供一个通用的 Traverse 实例,但是 Bitraverse 可以让你在其中获取两种类型。
【解决方案2】:

您可以使用 Scalaz 7 为元组提供 Bitraverse 实例的事实,然后像往常一样进行排序(但使用 bisequence 而不是 sequence):

scala> import scalaz._, std.option._, std.tuple._, syntax.bitraverse._
import scalaz._
import std.option._
import std.tuple._
import syntax.bitraverse._

scala> val p: (Option[Int], Option[String]) = (Some(1), Some("a"))
p: (Option[Int], Option[String]) = (Some(1),Some(a))

scala> p.bisequence[Option, Int, String]
res0: Option[(Int, String)] = Some((1,a))

不幸的是,Scalaz 7 目前需要这里的类型注释。


Yo Eight 在评论中指出,类型注释在这里仍然是强制性的。我不确定他或她的推理是什么,但实际上编写自己的包装器非常容易,它将为任何适当类型的元组提供bisequence 方法并且不需要类型注释:

import scalaz._, std.option._, std.tuple._    

class BisequenceWrapper[F[_, _]: Bitraverse, G[_]: Applicative, A, B](
  v: F[G[A], G[B]]
) {
  def bisequence = implicitly[Bitraverse[F]].bisequence(v)
}

implicit def bisequenceWrap[F[_, _]: Bitraverse, G[_]: Applicative, A, B](
  v: F[G[A], G[B]]
) = new BisequenceWrapper(v)

现在(some(1), some("a")).bisequence 可以正常编译了。

我想不出 Scalaz 不包含此类内容的充分理由。在此期间是否要添加它是个人喜好问题,但让编译器在这里进行输入绝对没有理论上的障碍。

【讨论】:

  • 不幸的是,在这种情况下类型注释仍然是强制性的
  • @YoEight:你为什么这么说?请参阅我的编辑以获取工作示例。
  • @Travis Brown。你只是推迟了问题。您的包装器为您保存类型信息。您将在呼叫站点注释的相同信息。当我将 MonadWriter 推送到 Scalaz 时,我做了同样的伎俩。
  • @YoEight:我不明白。它确实有效,因为隐式启动,包装器被构建,方法被调用,所有这些都没有类型注释。包装器所做的就是将我们拥有正确的BitraverseApplicative 实例的证据汇总在一起。
  • @Travis Brown。我不是说它不起作用。就像我说的,我对 MonadWriter 使用了相同的技巧。正如您在隐式解析后所知道的,您的代码类似于:bisequenceWrap[Tuple2, Option, Int, String]((some(1), some("a"))).bisequence。我的观点是:不是因为你看不到它,它不存在。此外,您的包装器在这种情况下将不起作用: val v: (String \/ Int, String \/ String) = (\/-(1), \/-("a")); v.bisequence
【解决方案3】:

我认为cats版本在这里不会是多余的。

@ import cats.implicits._
import cats.implicits._

@ (4.some, 2.some).bisequence
res1: Option[(Int, Int)] = Some((4, 2))

@ (4.some, none).bisequence
res2: Option[Tuple2[Int, Nothing]] = None

【讨论】:

    【解决方案4】:
    • Scala 2.13 开始,Option#zip 在标准库中提供了这种确切的行为:

      Some(2) zip Some('b') // Some((2, 'b'))
      Some(2) zip None      // None
      None zip Some('b')    // None
      None zip None         // None
      
    • Scala 2.13 之前,Option#zip 返回一个Iterable,并且可以将它与headOption 结合使用:

      Some(2) zip Some('b') headOption // Some((2, 'b'))
      Some(2) zip None headOption      // None
      

    【讨论】:

      【解决方案5】:
      scala> import scalaz._
      import scalaz._
      
      scala> import Scalaz._
      import Scalaz._
      
      scala> (Tuple2.apply[Int, Int] _).lift[Option].tupled
      res5: (Option[Int], Option[Int]) => Option[(Int, Int)] = <function1>
      
      scala> res5((some(3), some(11)))
      res6: Option[(Int, Int)] = Some((3,11))
      
      scala> res5((some(3), none))
      res7: Option[(Int, Int)] = None
      

      【讨论】:

      • 这很好,但我认为使用元组可比特遍历这一事实会更好一些。
      • Fwiw,这看起来很难看,因为 Scala 在很多情况下都无法推断类型。在 Haskell 中,res5 = uncurry $ liftA2 (,).
      猜你喜欢
      • 1970-01-01
      • 2011-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-20
      • 2015-02-06
      • 1970-01-01
      相关资源
      最近更新 更多