3

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?

Community
  • 1
  • 1
Frank S. Thomas
  • 4,725
  • 2
  • 28
  • 47
  • I see you changed your question. Could you explain what you exactly want to achieve? – EECOLOR Feb 19 '13 at 19:43
  • @EECOLOR I changed the title to better match the last sentence of my question. I'd like to have a solution that works for any type of any kind, and not only for `*` and `* -> *`. That another "explicit" implicit conversion seems to be necessary for e.g. `Map` is unsatisfying. I do have the feeling that just providing the appropriate typeclass instance should be enough to use `asOption`. Sorry for the previous misleading title! – Frank S. Thomas Feb 19 '13 at 20:14
  • I edited my answer to add another solution. This however might still not be what you are looking for. If it is not, could you try to explain what you _are_ looking for? – EECOLOR Feb 19 '13 at 21:15
  • @EECOLOR It seems that I found one answer to my question by using `scalaz.Unapply`. This solution does not require any additional implicit conversions but only an instance of `IsEmpty`. – Frank S. Thomas Feb 20 '13 at 18:35

2 Answers2

4
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)
EECOLOR
  • 11,184
  • 3
  • 41
  • 75
  • An implicit conversion of an implicit parameter... interesting! I've never seen that. If `asOption0` should also work for e.g. `Map`, another conversion seems to be required: https://gist.github.com/fthomas/4979797 But this raises the question why Scalaz provides an instance of `IsEmpty[({type F[V] = Map[K,V]})#F]` instead of `IsEmpty[({type F[_] = Map[K,V]})#F]` for `Map`. – Frank S. Thomas Feb 18 '13 at 19:20
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 constructor M[_] applied to type A, along with a corresponding type class instance TC[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.

Frank S. Thomas
  • 4,725
  • 2
  • 28
  • 47