Как написать параметр scalaz.IsEmpty для универсальных типов

Я пытаюсь написать общий метод, который обертывает все, что имеет 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 (с помощью >введите 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]

Можно ли написать один метод, который работает для String, List и типов более высокого типа одновременно?


person Frank S. Thomas    schedule 17.02.2013    source источник
comment
Я вижу, вы изменили свой вопрос. Не могли бы вы объяснить, чего именно вы хотите достичь?   -  person EECOLOR    schedule 19.02.2013
comment
@EECOLOR Я изменил заголовок, чтобы он лучше соответствовал последнему предложению моего вопроса. Я хотел бы иметь решение, которое работает для любого типа, а не только для * и * -> *. Это другое явное неявное преобразование кажется необходимым, например. Map не удовлетворяет. У меня есть ощущение, что просто предоставления соответствующего экземпляра класса типов должно быть достаточно, чтобы использовать asOption. Извините за предыдущий вводящий в заблуждение заголовок!   -  person Frank S. Thomas    schedule 20.02.2013
comment
Я отредактировал свой ответ, чтобы добавить другое решение. Однако это может быть не то, что вы ищете. Если нет, не могли бы вы объяснить, что вы ищете?   -  person EECOLOR    schedule 20.02.2013
comment
@EECOLOR Кажется, я нашел один ответ на свой вопрос, используя scalaz.Unapply. Это решение не требует дополнительных неявных преобразований, а требует только экземпляра IsEmpty.   -  person Frank S. Thomas    schedule 20.02.2013


Ответы (2)


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 не заботится о содержимом списка, мы просто делаем метод asOption0 счастливым, предоставляя более подробную версию 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

Редактировать

Я еще раз взглянул на проблему и нашел решение, которое кажется немного чище. Боюсь, это не тот результат, который ищет ОП.

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)
person EECOLOR    schedule 17.02.2013
comment
Неявное преобразование неявного параметра... интересно! Я никогда этого не видел. Если asOption0 также должен работать, например. Map, кажется, требуется другое преобразование: gist.github.com/fthomas/4979797 Но это поднимает вопрос, почему Scalaz предоставляет экземпляр IsEmpty[({type F[V] = Map[K,V]})#F] вместо IsEmpty[({type F[_] = Map[K,V]})#F] для Map. - person Frank S. Thomas; 18.02.2013

С помощью 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.

person Frank S. Thomas    schedule 20.02.2013