2

The following piece of code does not work:

trait A {
  type C

  val Extract: {
    def unapply(c: C): Option[Int] 
  }
}

The error is the following:

error: Parameter type in structural refinement may not refer to an abstract type defined outside that refinement

I wanted to write the above so that I force the user of trait A to define a object or a value which has an unapply method, so that I can use it in pattern matching.
I found two workarounds thanks to "Parameter type in structural refinement may not refer to an abstract type defined outside that refinement" and Scala: "Parameter type in structural refinement may not refer to an abstract type defined outside that refinement" (see below), but the first one makes inheritance of a trait compulsory, which is not feasible if I try to link to external libraries; and for the second, I loose the possibility to overriding Extract to put more methods into it.

Workarounds

trait A {
  type C

  trait Extractable {
    def unapply(c: C): Option[Int]
  }

  val Extract: Extractable
}

trait A {
  type C

  def extract(c: C): Option[Int]

  object Extract {
    def unapply(c: C): Option[Int]  = extract(c)
  }
}

Is there any way (maybe other than structural types) that I can express the idea that the user can implement Extract the way he wants, provided it has a method unapply of the requested signature?

Community
  • 1
  • 1
Mikaël Mayer
  • 10,425
  • 6
  • 64
  • 101

1 Answers1

1

Did you consider type classes? A simply states that its type parameter must be "extractable", that is, there must be an implicit value of type Extractable[C] in scope (which only demands that you implement unapply). This can come from you, from some other library or from the user himself.

This however means that you can't simultaneously have more than one Extractable[C] for a given type C; that is, there is only one implementation for Extractable for some given type C (e.g. Int). Is this aligned with your use case?

trait Extractable[C] {
  def unapply(c: C): Option[Int]
}

// user can supply his own extractable:
implicit val myExtractableInt = new Extractable[Int] {
  def unapply(c: Int): Option[Int] = Some(c)
}

// class A is parameterized by some C 
// for which Extractable[C] must exist
class A[C : Extractable] {
  val c: C = ???
  implicitly[Extractable[C]].unapply(c)
}

EDIT:

To make A a trait, we need a (yet another) workaround:

trait Extractable[C] {
  def unapply(c: C): Option[Int]
}

// class A is parameterized by C
// for which Extractable[C] must exist
trait A[C] {
  implicit def e: Extractable[C]
  val c: C = ???
  e.unapply(c)
}

class SomeClass extends A[Int] {
  // we need an implicit Extractable[Int],
  // either from some import or custom one
  implicit def e = new Extractable[Int] {
    def unapply(c: Int): Option[Int] = Some(c)
  }
}
slouc
  • 9,508
  • 3
  • 16
  • 41
  • Yes that would almost work, except I need it to be a trait because there are several like `A` that I need to mix in one into another. And traits may not have implicit parameters nor parameters at all. – Mikaël Mayer May 10 '17 at 12:50
  • 1
    Does edited version help? It's still a workaround, but I think you should be pretty flexible with this. – slouc May 10 '17 at 12:59
  • Can you please use the same name as in the question? Instead of `e`, write `Extract` so that one can use it in pattern matching in `A`. Furthermore, it would be better to put the `Extractable` trait in `A` to be able to not disclose it somewhere else that the children of `A` – Mikaël Mayer May 11 '17 at 13:27