问题
I am trying to write a generic method that wraps anything that has an scalaz.IsEmpty typeclass instance into an Option
. It should return None
for empty values, and wrap it into Some
if it is non-empty. Here's what I've come up so far:
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
works for primitive types like String
(by using a type lambda to indicate that C
has the shape B[_]
) and asOption1
works for types with an unary type constructor like 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]
Is it possible to write one method that works for String
, List
, and types of higher kind at the same time?
回答1:
scala> asOption0(List(1,2,3))
<console>:17: error: could not find implicit value for parameter
ev: scalaz.IsEmpty[[A]List[Int]]
This error tells you that it can not find an IsEmpty
instance for a list, and that is because the type parameter doesn't matter. Scalaz has an implicit for any list, regardless of the type parameter.
The method requests an IsEmpty[List[Int]]
and Scalaz only has one available for IsEmpty[List[_]]
. Since IsEmpty
doesn't care about the contents of list, we just make the asOption0
method happy by supplying a more detailed version of IsEmpty
:
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
Edit
I took another look at the problem and found a solution that seems a bit cleaner. I fear it's not the result the OP is looking for.
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)
回答2:
With the help of scalaz.Unapply it is possible to write a generic asOption
that works for many different types (those that are supported by Unapply
) and that does not require any additional implicit conversions:
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))
Here is the first part of Unapply
's docstring:
Represents a type
MA
that has been destructured into as a type constructorM[_]
applied to typeA
, along with a corresponding type class instanceTC[M]
.The implicit conversions in the companion object provide a means to obtain type class instances for partially applied type constructors, in lieu of direct compiler support as described in SI-2712.
来源:https://stackoverflow.com/questions/14924707/how-to-write-a-scalaz-isempty-parameter-for-generic-types