Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constraining Type Signatures for Left and Right

Tags:

scala

Given the following AST for Success and Failure:

sealed trait Success
case object FooGood extends Success
case object BarGood extends Success

sealed trait Failure
case object FooBad extends Failure
case object BarBad extends Failure

And the method signature:

def go[A <: Failure, B <: Success](x: Int): Either[A, B] = ???

However, I'd like to constrain the Left and Right types to be specific to Foo or Bar.

But the following code compiles (against my wishes):

scala> go[FooBad.type, BarGood.type](5)
scala.NotImplementedError: an implementation is missing

How can I achieve this constraint at compile-time?

like image 202
Kevin Meredith Avatar asked Sep 16 '15 14:09

Kevin Meredith


2 Answers

Here's a solution that relies on a type class. Of note is that it does not require to manually define new type class instances for each (pair of) AST node. It does involve introducing a common super type for each pair (though you don't technically have to use it as a base class, it's merely used as a tag type).

sealed trait Success[T]
abstract sealed class Foo
abstract sealed class Bar
case object FooGood extends Foo with Success[Foo]
case object BarGood extends Bar with Success[Bar]
sealed trait Failure[T]
case object FooBad extends Foo with Failure[Foo]
case object BarBad extends Bar with Failure[Bar]

@annotation.implicitNotFound("Expecting reciprocal Failure and Success alternatives, but got ${A} and ${B}")
trait IsGoodAndBadFacet[A,B]
implicit def isGoodAndBadFacet[T,A,B](implicit e1: A <:< Failure[T], e2: B<:<Success[T]): IsGoodAndBadFacet[A,B] = null

def go[A, B](x: Int)(implicit e: IsGoodAndBadFacet[A,B]): Either[A, B] = ???

Repl test:

scala> go[FooBad.type, BarGood.type](5)
<console>:17: error: Expecting reciprocal Failure and Success alternatives, but got FooBad.type and BarGood.type
              go[FooBad.type, BarGood.type](5)
                                           ^

scala> go[FooBad.type, FooGood.type](5)
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:225)
  at .go(<console>:11)
  ... 33 elided

scala> go[BarBad.type, BarGood.type](5)
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:225)
  at .go(<console>:11)
  ... 33 elided
like image 106
Régis Jean-Gilles Avatar answered Nov 13 '22 19:11

Régis Jean-Gilles


The problem you have is that compiler doesn't know that FooGood is somehow related to FooBad, so you need to hint it somehow.

Here's what I came up with, though I admit it is not very elegant:

trait Grouping[B, G]

object FooHelper {
  implicit object fooGrouping Grouping[FooBad.type, FooGood.type]
}

object BarHelper {
  implicit object barGrouping Grouping[BarBad.type, BarGood.type]
}

def go[A <: Failure, B <: Success](x: Int)(implicit ev: Grouping[A, B]): Either[A, B] = ???


import FooHelper._
import BarHelper._

// the following two type check
go[FooBad.type, FooGood.type](5)
go[BarBad.type, BarGood.type](5)

// while these two do not
go[FooBad.type, BarGood.type](5)
go[BarBad.type, FooGood.type](5)

As you can see the hint is implemented by creating a Grouping and putting correct groupings into the implicit scope. The problem with this approach is that the user might create his own groupings which might not be valid.

like image 24
Ihor Kaharlichenko Avatar answered Nov 13 '22 18:11

Ihor Kaharlichenko