【问题标题】:How to write a scalaz.IsEmpty parameter for generic types如何为泛型类型编写 scalaz.IsEmpty 参数
【发布时间】:2013-02-17 18:46:31
【问题描述】:

我正在尝试编写一个通用方法,它将具有scalaz.IsEmpty 类型类实例的任何内容包装到Option 中。它应该为空值返回None,如果它是非空的,则将其包装到Some。到目前为止,这是我想出的:

import scalaz._
import Scalaz._

def asOption0[C](c: C)(implicit ev: IsEmpty[({ type B[A] = C })#B]) =
  if (ev.isEmpty(c)) None else Some(c)

def asOption1[A, C[_]](c: C[A])(implicit ev: IsEmpty[C]) =
  if (ev.isEmpty(c)) None else Some(c)

asOption0 适用于 String 之类的原始类型(通过使用 type lambda 来表示 C 的形状为 B[_])和 asOption1 适用于具有一元类型构造函数的类型,例如 List

scala> asOption0("")
res1: Option[String] = None

scala> asOption1(List(1,2,3))
res0: Option[List[Int]] = Some(List(1, 2, 3))

scala> asOption0(List(1,2,3))
<console>:17: error: could not find implicit value for parameter
                     ev: scalaz.IsEmpty[[A]List[Int]]

scala> asOption1("hello")
<console>:17: error: could not find implicit value for parameter
                     ev: scalaz.IsEmpty[Comparable]

是否可以编写一种同时适用于StringList 和更高类型的方法?

【问题讨论】:

  • 我看到你改变了你的问题。你能解释一下你到底想达到什么目标吗?
  • @EECOLOR 我更改了标题以更好地匹配我问题的最后一句。我想要一个适用于任何类型的解决方案,不仅适用于** -&gt; *。另一个“显式”隐式转换似乎是必要的,例如Map 不满意。我确实觉得只需提供适当的类型类实例就足以使用asOption。抱歉之前的误导性标题!
  • 我编辑了我的答案以添加另一个解决方案。然而,这可能仍然不是您想要的。如果不是,您能否尝试解释一下您在寻找什么?
  • @EECOLOR 似乎我通过使用scalaz.Unapply 找到了我的问题的一个答案。此解决方案不需要任何额外的隐式转换,只需要 IsEmpty 的一个实例。

标签: scala generics typeclass scalaz higher-kinded-types


【解决方案1】:
scala> asOption0(List(1,2,3))
<console>:17: error: could not find implicit value for parameter
                     ev: scalaz.IsEmpty[[A]List[Int]]

这个错误告诉你它找不到列表的IsEmpty 实例,这是因为类型参数无关紧要。 Scalaz 对任何列表都有一个隐式,无论类型参数如何。

该方法请求一个IsEmpty[List[Int]],而Scalaz 只有一个可用于IsEmpty[List[_]]。由于IsEmpty 不关心列表的内容,我们只是通过提供更详细的IsEmpty 版本来使asOption0 方法快乐:

def asOption0[C](c: C)(implicit ev: IsEmpty[({ type B[_] = C })#B]) =
  if (ev.isEmpty(c)) None else Some(c)  

implicit def detailedIsEmpty[A, C[_]](implicit ev: IsEmpty[C]) =
  ev.asInstanceOf[IsEmpty[({ type B[_] = C[A] })#B]]


asOption0("test")             //> res0: Option[String] = Some(test)
asOption0(List(1, 2, 3))      //> res1: Option[List[Int]] = Some(List(1, 2, 3))
asOption0("")                 //> res2: Option[String] = None
asOption0(List[Int]())        //> res3: Option[List[Int]] = None

编辑

我又看了一下这个问题,发现了一个看起来更简洁的解决方案。我担心这不是 OP 正在寻找的结果。

trait IsEmptyLike[F] {
  def isEmpty(fa: F): Boolean
}

object IsEmptyLike {

  implicit def case0[A](implicit ev: IsEmpty[({ type B[_] = A })#B]) =
    new IsEmptyLike[A] {
      def isEmpty(fa: A): Boolean = ev.isEmpty(fa)
    }
  implicit def case1[A[_], B](implicit ev: IsEmpty[A]) =
    new IsEmptyLike[A[B]] {
      def isEmpty(fa: A[B]): Boolean = ev.isEmpty(fa)
    }
  implicit def case2[A[_, _], B, C](implicit ev: IsEmpty[({ type D[X] = A[B, X] })#D]) =
    new IsEmptyLike[A[B, C]] {
      def isEmpty(fa: A[B, C]): Boolean = ev.isEmpty(fa)
    }
}

def asOption[C](c: C)(implicit ev: IsEmptyLike[C]) =
  if (ev.isEmpty(c)) None else Some(c)

【讨论】:

  • 隐式参数的隐式转换...有趣!我从来没有见过。如果asOption0 也应该适用于例如Map,似乎需要另一个转换:gist.github.com/fthomas/4979797 但这提出了一个问题,为什么 Scalaz 为 Map 提供了 IsEmpty[({type F[V] = Map[K,V]})#F] 而不是 IsEmpty[({type F[_] = Map[K,V]})#F] 的实例。
【解决方案2】:

scalaz.Unapply 的帮助下,可以编写一个通用的asOption,它适用于许多不同的类型(Unapply 支持的类型)并且不需要任何额外的隐式转换:

import scalaz._
import Scalaz._

def asOption[MA](ma: MA)(implicit U: Unapply[IsEmpty, MA]): Option[MA] =
  if (U.TC.isEmpty(U(ma))) None else Some(ma)

asOption("")              //> res0: Option[String] = None
asOption("hello")         //> res1: Option[String] = Some(hello)

asOption(List[Int]())     //> res2: Option[List[Int]] = None
asOption(List(1,2))       //> res3: Option[List[Int]] = Some(List(1, 2))

asOption(Map[Int,Int]())  //> res4: Option[Map[Int,Int]] = None
asOption(Map(1 -> 2))     //> res5: Option[Map[Int,Int]] = Some(Map(1 -> 2))

这是Unapply的文档字符串的第一部分:

表示一个类型MA,它已被解构为类型构造函数M[_] 应用于类型A,以及对应的类型类实例TC[M]

伴生对象中的隐式转换提供了一种获取类型类的方法 部分应用类型构造函数的实例,代替直接编译器支持 如SI-2712 中所述。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-23
    • 2021-03-22
    • 1970-01-01
    • 1970-01-01
    • 2016-10-04
    • 1970-01-01
    相关资源
    最近更新 更多