0

I am encountering a number of use cases where I am end of attempting to write Functor, Applicative, Monad, etc instances in contexts where I am also using type parameter bounds.

For example...

import cats._

trait Preference[A] extends Order[A]

trait SocialWelfareFunction[-CC <: Iterable[P], +P <: Preference[_]]
  extends (CC => P)


object SocialWelfareFunction {

  def functor: Functor[({ type F[P <: Preference[_]] = SocialWelfareFunction[Iterable[P], P] })#F] = {
    ???
  }

...when I try to compile this I will get the following error.

kinds of the type arguments ([P <: Playground.this.Preference[_]]Playground.this.SocialWelfareFunction[Iterable[P],P]) do not conform to the expected kinds of the type parameters (type F) in trait Monad.
[P <: Playground.this.Preference[_]]Playground.this.SocialWelfareFunction[Iterable[P],P]'s type parameters do not match type F's expected parameters:
type P's bounds <: Playground.this.Preference[_] are stricter than type _'s declared bounds >: Nothing <: Any

How can I write Functor, Applicative, Monad , etc instances for contexts in which I am also using type parameters? Is it even possible? Is there a more appropriate way forward?

davidrpugh
  • 4,363
  • 5
  • 32
  • 46
  • 1
    If `SocialWelfareFunction[C, ?]` is supposed to be a `Functor` in the second component, then it must have a `map` that accepts an `f: A => B` and maps `SocialWelfareFunction[C, A]` to `SocialWelfareFunction[C, B]`. Can you envision a `map`-implementation that can map a `Preference[_]` to `List[Int]` or `JsonNode` or `(Unit, Preference[_])` or `Either[String, Double]`? If not, then it's not a functor. Picking the types `A` & `B` carefully wouldn't help much, because many interesting constructions on functors and monads rely on the functor `F` being able to map `F[A]` to e.g. `F[Either[X, A]]`. – Andrey Tyukin Jul 24 '18 at 08:17
  • Perhaps what I really want to say is not that `P <: Preference[A]` but that `P` has an instance of the type class `Order[A]` defined. Would this be possible? – davidrpugh Jul 24 '18 at 08:37
  • Here is an [example with typeclass](https://stackoverflow.com/questions/48725356/how-to-implement-functordataset/48726850#48726850) (there, an `Encoder[A]` was required instead of an `Order[A]`, but it's somewhat similar). Actually, it should be possible in a similar way to get a "functor" for type constructors that have bounds on parameters. I'll sketch a proposal (will prob. take a few minutes). – Andrey Tyukin Jul 24 '18 at 08:45
  • I have a general demo of how to define `Functor` instances for type constructors that have type bounds and depend on typeclasses, but I still don't understand what you wanted to achieve there with that `SocialWelfareFunction`. It seems that you wanted to define a functor `F[P] = (Iterable[P] => P)`, which is impossible (because `P` is both an input and output, which makes it very similar to `Endomorphisms[P]`, which is also not a functor, regardless of type system or language). – Andrey Tyukin Jul 24 '18 at 09:35
  • I am learning FP by building a library for auction simulation. It is very possible that my `SocialWelfareFunction` is simply not a functor. Perhaps I can find a better example to demonstrate the interaction between type parameter bounds (or type class bounds) when creating functors which is the focus of this question. – davidrpugh Jul 24 '18 at 09:48
  • Can you post your demo? Perhaps seeing the demo can help me iterate the question in a better direction... – davidrpugh Jul 24 '18 at 09:50
  • 1
    @davidpugh Posted the generic solution for "almost functors" with upper type bounds and type classes. It's essentially the same as in the answer linked previously. I think I will convert it into a "canonical" Q&A-pair to which I can link later, because this question seems to come up repeatedly. – Andrey Tyukin Jul 24 '18 at 10:03

1 Answers1

0

(Not a full solution, just a hint requested by OP to further clarify the question)

This example shows how to define Functor instances for type constructors that look almost as if they could be functors, but have several restrictions:

  1. Upper type bound <: UB on type parameter
  2. Require instances of typeclass TC

Here is a way around these two restrictions:

import scala.language.higherKinds

// Your favorite flavour of `Functor`,
// e.g. from `scalaz` or `cats`
trait Functor[F[_]] {
  def map[A, B](x: F[A], f: A => B): F[B]
}

// an upper bound
trait UB

// a typeclass
trait TC[X]


// A type constructor that is not a 
// functor, because it imposes upper bounds
// on the parameter, and because it requires
// a typeclass `TC` for `X`.
class Foo[X <: UB : TC] {
  def map[Y <: UB : TC](f: X => Y): Foo[Y] = ??? /* 
    some very clever implementation making use of `UB` and `TC`
  */
}

// A Functor that approximates `Foo[X]` for *arbitrary* `X`,
// without any restrictions.
abstract class WrappedFoo[X] { outer =>
  type Base <: UB
  val base: Foo[Base]

  protected val path: Base => X

  def map[Y](f: X => Y): WrappedFoo[Y] = new WrappedFoo[Y] {
    type Base = outer.Base
    val base = outer.base
    val path: Base => Y = outer.path andThen f
  }

  def unwrap[Y <: UB](
    implicit
    xIsY: X =:= Y,
    yTC: TC[Y]
  ): Foo[Y] = base.map(outer.path andThen xIsY)
}

// Functor instance for `WrappedFoo`
object WrappedFooFunctor extends Functor[WrappedFoo] {
  def map[A, B](x: WrappedFoo[A], f: A => B): WrappedFoo[B] = {
    x map f
  }
  def wrap[A <: UB](foo: Foo[A]): WrappedFoo[A] = new WrappedFoo[A] {
    type Base = A
    val base = foo
    val path = identity[A]
  }
}

object Example {

  // two "good" classes that conform to 
  // the upper bound and have instances
  // of the typeclass
  class Good1 extends UB
  class Good2(i: Int) extends UB

  implicit object Good1TC extends TC[Good1]
  implicit object Good2TC extends TC[Good2]

  val x1 = new Foo[Good1]       // start with "Foo[Good1]"
  val f: Good1 => Int = _.toString.length
  val g: Int => Good2 = i => new Good2(i)

  // Now we would like to go like this:
  // 
  //   Foo[Good1] ---f---> Foo[Int] ---g---> Foo[Good2]
  // 
  // The problem is: `Int` does not conform to `UB`,
  // and has no `TC` instance.

  // Solution:
  val x1w = WrappedFooFunctor.wrap(x1)
  val intermediate = x1w.map(f) // approximates "Foo[Int]"
  val x2w = intermediate.map(g) // wraps "Foo[Good2]"
  val x2 = x2w.unwrap           // only "Foo[Good2]"
}
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93