问题
I have a class Foo
with two parameters, and I am trying to write a Functor instance for Foo with the first parameter fixed, as follows:
object Scratchpad {
trait Functor[F[_]] {
def fmap[A, B](f: A => B): F[A] => F[B]
}
case class Foo[X, Y](value: Y)
implicit def fooInstances[X]: Functor[Foo[X, _]] =
new Functor[Foo[X, _]] {
def fmap[A, B](f: A => B): Foo[X, A] => Foo[X, B] =
foo => Foo[X, B](f(foo.value))
}
}
But the above code fails to compile, generating the following error:
Error:(9, 41) Scratchpad.Foo[X, _] takes no type parameters, expected: one
implicit def fooInstances[X]: Functor[Foo[X, _]] =
I know Scalaz does something like this with their \/
type, but inspection of their source code reveals an odd ?
, which doesn't compile for me:
implicit def DisjunctionInstances1[L]: Traverse[L \/ ?] with Monad[L \/ ?] with BindRec[L \/ ?] with Cozip[L \/ ?] with Plus[L \/ ?] with Optional[L \/ ?] with MonadError[L \/ ?, L] =
How does the Scalaz ?
work, and how can I write a Functor instance for Foo
?
回答1:
You're looking to partially apply a type-level constructor. Unfortunately, we can't directly do that. However, we can still do so indirectly using a little feature called Structural Types. In order to turn Foo
from a two-argument type constructor into a one-argument type constructor, we'll define a type synonym inside of an anonymous, structural type.
implicit def fooInstances[X]: Functor[({ type T[A] = Foo[X, A] })#T] =
new Functor[({ type T[A] = Foo[X, A] })#T] {
// ...
}
The braces {}
in a type context define an anonymous type that we're exploiting to essentially create a lambda function at the type level. We define an anonymous type with an alias inside of it, then immediately evaluate that alias.
回答2:
but inspection of their source code reveals an odd ?, which doesn't compile for me
?
comes from the kind-projector project, which is a Scala compiler plugin you need to add to your build.sbt
:
resolvers += Resolver.sonatypeRepo("releases")
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.4")
Which prettifies the creation of type lambdas for you:
implicit def fooInstances[X]: Functor[Foo[X, ?]] =
new Functor[Foo[X, ?]] {
def fmap[A, B](f: A => B): Foo[X, A] => Foo[X, B] =
foo => Foo[X, B](f(foo.value))
}
Remember that we can also use partial type application with type aliases:
implicit def fooInstances[X] = {
type PartiallyAppliedFoo[A] = Foo[X, A]
new Functor[PartiallyAppliedFoo] {
override def fmap[A, B](f: (A) => B): (PartiallyAppliedFoo[A]) => PartiallyAppliedFoo[B] = foo => Foo[X, B](f(foo.value))
}
}
来源:https://stackoverflow.com/questions/45271911/functor-instance-for-type-constructor-with-two-parameters-in-scala