There are two issues getting in the way, one with the scoping of view and context bounds, the other with type erasure.
1. Scoping
These:
case class GreaterThan[T <% Ordered[T]]( a:Expr[T], b:Expr[T]) extends Expr[Boolean]
case class GreaterThan[T : Ordering]( a:Expr[T], b:Expr[T]) extends Expr[Boolean]
Are syntactic sugar for:
case class GreaterThan[T]( a:Expr[T], b:Expr[T])(implicit evidence: T => Ordered[T]) extends Expr[Boolean]
case class GreaterThan[T]( a:Expr[T], b:Expr[T])(implicit ordering: Ordering[T]) extends Expr[Boolean]
The implicit parameters are scoped inside of the case class and aren't accessible outside, as you discovered when you tried a solution with Ordering
. Inside of your match
statement, it can't get at the >
from Ordered[T]
.
2. Type Erasure
With this statement:
case GreaterThan(a,b) => execute(a) > execute(b)
At run-time the code can discover that the Expr being matched on is a GreaterThan
, however because of type erasure there is no way for it to know what the type parameter for this particular GreaterThan
is. Even if it could, this wouldn't take it very far, because views and context bounds are resolved statically - all the work is done at compile-time. With the solution with Ordered
, the compiler has to find an appropriate T => Ordered[T]
to pass to the constructor of GreaterThan
at compile-time. None of that resolution can happen at run-time inside of execute
however, and the same T => Ordered[T]
might not even be in scope.
3. Solution
There isn't any way to fix this without exposing the implicit outside of GreaterThan
. You could do:
case class GreaterThan[T]( a:Expr[T], b:Expr[T])(implicit val ordering: Ordering[T]) extends Expr[Boolean]
The val
in front of ordering
will make it accessible outside.
Then in the match
:
case gt: GreaterThan[_] => gt.ordering.gt(execute(gt.a), execute(gt.b))
We don't know what the type parameter for the GreaterThan
is at this point, but we do know that both subexpressions and the Ordering
are parameterized with the same type, and so we can safely do the comparison.
My knowledge of Haskell's internals aren't as deep, but I believe generic types actually carry a reference to a table holding the methods for their typeclasses. We are doing the same thing here with Scala, but we have to explicitly pass around the type class object.
Another solution would be to use the Visitor pattern, but that would take you even farther from the original Haskell.