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

436 views Asked by At

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.

1

There are 1 answers

2
Dmytro Mitin On BEST ANSWER

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.