类型 * 类型的类型类实例,其中需要类型 * -> *

Instance of typeclass for types of kind * where a type of kind * -> * is expected

我正在尝试为 String 创建 scalaz 的 IsEmpty 类型类的实例。这是我到目前为止所得到的:

implicit val stringIsEmpty = new IsEmpty[({ type t[+A] = String })#t] {
  def isEmpty[A](fa: String) = ???
  def empty[A] = ???
  def plus[A](a: String, b: => String) = ???
}

def f[F[_]: IsEmpty, A](fa: F[A]): F[A] = fa

现在,String 属于 *IsEmpty[F] 期望 F 属于 * -> *,因此 lambda 类型 f[+A] = String.

这有效,但前提是字符串输入为 t,即:

// doesn't compile
f("hello")

// compiles if I extract `t` into a type alias and annotate the string with it
type t[+A] = String
implicit val stringIsEmpty = new IsEmpty[t] { /** **/ }

f("hello": t[Nothing])

有没有什么方法可以实现 IsEmpty,然后我可以像往常一样将 f 应用于字符串?

事实证明,您可以使用 Unapply 魔法将类型 * 转换为类型 * -> *.

这是我的解决方案:

def f[F[_]: IsEmpty, A](fa: F[A]): F[A] = fa

def fU[FA](fa: FA)(implicit U: Unapply[IsEmpty, FA]) =
  f[U.M, U.A](U(fa))(U.TC)

现在你可以做到 fU("hello")。 return 类型也将被正确推断为 String.

据我所知,你确实需要这个辅助功能,这有点痛苦,但我想这就是缺少 direct compiler support.

的代价

当我意识到 String 有一个 Applicative 的实例并且你可以做类似 "hello".replicateM(3).

的事情时,我有了这个想法

我这样具体化了这个表达式:

scala> import scala.reflect.runtime.universe
import scala.reflect.runtime.universe

scala> universe.reify("hello".replicateM(3)).tree
res32: reflect.runtime.universe.Tree = Scalaz.ToApplicativeOpsUnapply("hello")(Unapply.unapplyA(Applicative.monoidApplicat
ive(Scalaz.stringInstance))).replicateM(3)

并且ToApplicativeOpsUnapply揭开了幕后的秘密:

implicit def ToApplicativeOpsUnapply[FA](v: FA)(implicit F0: Unapply[Applicative, FA]) =
  new ApplicativeOps[F0.M,F0.A](F0(v))(F0.TC)