Search code examples
scalatypeclasspolymorphic-functions

Defining a constructor for a typeclass that takes a method with a type parameter?


I have a situation where none of the solutions that I'm aware of feel like good ones. I am trying to define a typeclass, like the example below, where it has an abstract type S that must implement another typeclass (not shown) Valid[A]. It feels like the easiest way to do this would be to create an apply constructor, which will force the user to enter S and automatically bring in an implicit instance for sIsValid.

The problem with this is that function changeType takes a type parameter. Despite some googling I have not figured out how to write a type annotation for a function that takes a type parameter (this may be because it seems like Scala does not allow anonymous functions with type parameters). Does my approach here seem like a sensible one? If I can provide a type annotation for changeType then the typeclass user can still pass a non-anonymous function to the apply constructor, which seems like a mostly satisfactory solution.

abstract class IsTC[A[_], T] {
  // type S is an abstract type rather than type parameter, but must implement Valid[A]
  type S
  implicit val sIsValid: Valid[S]

  def get(self: A[T], i: Int): T
  def changeType[_T]: A[_T]
}
object IsTC {
  def apply[A[_], T, _S](
    fget: (A[T], Int) => T,
    fchangeType:  // what should this type annotation be?
  ): IsTC[A, T] { type S = _S } = new IsTC[A, T] {
    type S = _S
    def get(self: A[T], i: Int) = fget(self, i)
    def changeType[_T]: A[_T] = fchangeType[_T]
  }
}

Any help/thoughts gratefully received.


Solution

  • Scala 2 doesn't support polymorphic functions. Polymorphic can be methods, not values. And functions are values. Polymorphic functions can be emulated with wrappers

    // for [A] => (a: A) => B[A]
    trait Poly {
      def apply[A](a: A): B[A]
    }
    

    or

    // for [A <: U] => (a: A) => B[A]
    trait Poly[U] {
      def apply[A <: U](a: A): B[A]
    }
    

    like shapeless.Poly. They generalize ordinary functions

    trait Function[A, B] {
      def apply(a: A): B
    }
    

    Try

    object IsTC {
      def apply[A[_], T, _S](
                              fget: (A[T], Int) => T,
                              fchangeType: FchangeType[A]
                            ): IsTC[A, T] { type S = _S } = new IsTC[A, T] {
        override type S = _S
        override implicit val sIsValid: Valid[_S] = ???
        override def get(self: A[T], i: Int) = fget(self, i)
        override def changeType[_T]: A[_T] = fchangeType[_T]
      }
    }
    
    trait FchangeType[A[_]] {
      def apply[X]: A[X]
    }
    

    Dotty has polymorphic functions [A <: U] => (a: A) => f[A](a) of polymorphic function types [A <: U] => A => B[A] but there is implementation restriction: polymorphic function types must have a value parameter so you can't have polymorphic function type [X] => A[X] (not to be confused with a type lambda [X] =>> A[X]) currently even there.

    Also (besides emulation with wrappers) some polymorphic types can be expressed via existential types: [A] => B[A] => C (for constant C) is actually B[_] => C.

    This is because ∀a: (B(a) => C)(∃a: B(a)) => C.