Functor Instance for Type Constructor with Two Parameters in Scala
Asked Answered
W

2

7

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?

Weisbrodt answered 24/7, 2017 at 2:50 Comment(1)
Related question What is a kind projectorBeestings
S
8

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.

Stedt answered 24/7, 2017 at 3:54 Comment(0)
M
10

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))
  }
}

Edit (05/04/2020)

Note the syntax for kind project has changed from ? to * for partially applied types, thus the above example should be:

sbt:

resolvers += Resolver.sonatypeRepo("releases")

addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.11.0")

Code:

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))
}
Mullein answered 24/7, 2017 at 6:41 Comment(3)
I wish I could checkmark two answers, since you addressed the ?. I guess that's my fault for asking two questions in one.Weisbrodt
@FriedBrice No problem, as long as you've got your answer that's what's important :)Mullein
Syntax switched from ? to *: github.com/typelevel/kind-projector/pull/109Beestings
S
8

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.

Stedt answered 24/7, 2017 at 3:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.