为值和该值的函子编写一个隐式 class
Writing a single implicit class for both a value and a functor of that value
我经常发现自己必须对一个值和该值的函子执行几乎相同的操作。我通常使用两个隐式 classes 来实现这一点,如下所示:
implicit class Apimped(a: A) {
def doSomething: B = ???
}
implicit class FApimped[F[_]: Functor](fa: F[A]) {
def doSomething: F[B] = Functor[F].map(fa)(a => a.doSomething)
}
那么我可以这样做,例如:
a.doSomething //B
Option(a).doSomething //Option[B]
但是,必须编写两个隐式 classes(通常针对每个值类型)来执行此操作似乎有点笨拙。我的问题是,是否只用一个隐式 class 就可以实现上述目标?也就是说,当您在值的函子上调用 doSomething
时,map
操作将是隐式的。谢谢。
我不知道它是否在Scalaz/Cats中(也许,不能保证它不存在),但原则上它确实有效。这是一个没有任何依赖的小demo,演示了原理。
假设您有来自 Scalaz 或 Cats 的这些类型类:
import scala.language.higherKinds
trait Functor[F[_]] {
def map[A, B](a: F[A])(f: A => B): F[B]
}
type Id[X] = X
implicit object IdFunctor extends Functor[Id] {
def map[A, B](a: A)(f: A => B): B = f(a)
}
implicit object OptionFunctor extends Functor[Option] {
def map[A, B](a: Option[A])(f: A => B) = a map f
}
然后您可以编写或在库中找到一个类似于以下装置的类型类:
trait EverythingIsAlwaysAFunctor[A, B, F[_]] {
def apply(a: A): F[B]
def functor: Functor[F]
}
object EverythingIsAlwaysAFunctor {
implicit def functorIsFunctor[A, F[_]](implicit f: Functor[F])
: EverythingIsAlwaysAFunctor[F[A], A, F] = {
new EverythingIsAlwaysAFunctor[F[A], A, F] {
def apply(fa: F[A]): F[A] = fa
def functor: Functor[F] = f
}
}
implicit def idIsAlsoAFunctor[A]
: EverythingIsAlwaysAFunctor[A, A, Id] = {
new EverythingIsAlwaysAFunctor[A, A, Id] {
def apply(a: A): Id[A] = a
def functor: Functor[Id] = implicitly[Functor[Id]]
}
}
}
这个东西做了以下事情:
- 如果值
A
已经是某个函子 F
的 F[B]
形状,则使用此函子
- 在所有其他情况下,它假装
A
实际上是 Id[A]
现在你可以用一个 doSomething
方法编写你的 DoSomething
-pimp-my-library-syntax 东西:
implicit class DoSomething[A, F[_]](a: A)(
implicit eiaaf: EverythingIsAlwaysAFunctor[A, Int, F]
) {
def doSomething: F[String] = eiaaf.functor.map(eiaaf(a))("*" * _)
}
然后它在所有情况下都有效:
val x = Option(42).doSomething
val y = 42.doSomething
println(x)
println(y)
打印:
Some(******************************************)
******************************************
我经常发现自己必须对一个值和该值的函子执行几乎相同的操作。我通常使用两个隐式 classes 来实现这一点,如下所示:
implicit class Apimped(a: A) {
def doSomething: B = ???
}
implicit class FApimped[F[_]: Functor](fa: F[A]) {
def doSomething: F[B] = Functor[F].map(fa)(a => a.doSomething)
}
那么我可以这样做,例如:
a.doSomething //B
Option(a).doSomething //Option[B]
但是,必须编写两个隐式 classes(通常针对每个值类型)来执行此操作似乎有点笨拙。我的问题是,是否只用一个隐式 class 就可以实现上述目标?也就是说,当您在值的函子上调用 doSomething
时,map
操作将是隐式的。谢谢。
我不知道它是否在Scalaz/Cats中(也许,不能保证它不存在),但原则上它确实有效。这是一个没有任何依赖的小demo,演示了原理。
假设您有来自 Scalaz 或 Cats 的这些类型类:
import scala.language.higherKinds
trait Functor[F[_]] {
def map[A, B](a: F[A])(f: A => B): F[B]
}
type Id[X] = X
implicit object IdFunctor extends Functor[Id] {
def map[A, B](a: A)(f: A => B): B = f(a)
}
implicit object OptionFunctor extends Functor[Option] {
def map[A, B](a: Option[A])(f: A => B) = a map f
}
然后您可以编写或在库中找到一个类似于以下装置的类型类:
trait EverythingIsAlwaysAFunctor[A, B, F[_]] {
def apply(a: A): F[B]
def functor: Functor[F]
}
object EverythingIsAlwaysAFunctor {
implicit def functorIsFunctor[A, F[_]](implicit f: Functor[F])
: EverythingIsAlwaysAFunctor[F[A], A, F] = {
new EverythingIsAlwaysAFunctor[F[A], A, F] {
def apply(fa: F[A]): F[A] = fa
def functor: Functor[F] = f
}
}
implicit def idIsAlsoAFunctor[A]
: EverythingIsAlwaysAFunctor[A, A, Id] = {
new EverythingIsAlwaysAFunctor[A, A, Id] {
def apply(a: A): Id[A] = a
def functor: Functor[Id] = implicitly[Functor[Id]]
}
}
}
这个东西做了以下事情:
- 如果值
A
已经是某个函子F
的F[B]
形状,则使用此函子 - 在所有其他情况下,它假装
A
实际上是Id[A]
现在你可以用一个 doSomething
方法编写你的 DoSomething
-pimp-my-library-syntax 东西:
implicit class DoSomething[A, F[_]](a: A)(
implicit eiaaf: EverythingIsAlwaysAFunctor[A, Int, F]
) {
def doSomething: F[String] = eiaaf.functor.map(eiaaf(a))("*" * _)
}
然后它在所有情况下都有效:
val x = Option(42).doSomething
val y = 42.doSomething
println(x)
println(y)
打印:
Some(******************************************)
******************************************