How to compare Ordered abstract type in Scala trait?
Asked Answered
S

1

5

Given the code below the method foo should compare operator-wise a given parameter bar with the lowerBound and upperBound all being of the same abstract type Bar.

trait Foo {
  type Bar <: Ordered[Bar]
  val lowerBound: Bar
  val upperBound: Bar
  def foo(bar: Bar) = bar >= lowerBound && bar <= upperBound
}

This way the trait Foo can be defined. The problems start with the below concrete class FooImpl.

class FooImpl extends Foo {
  type Bar = Int
  val lowerBound = 0
  val upperBound = 5
}

I understand that scala.Int isn't implementing what scala.runtime.RichInt does, effectively scala.math.Ordered[Int]. Defining type Bar as RichInt instead neither works as it does not conform to scala.math.Ordered[RichInt]. My third attempt to define type Bar as Ordered[Ord] where Ord is declared as type Ord and defining it in FooImpl as Int also did not work.

How would a possibly close solution look like?

Spindling answered 13/2, 2012 at 0:11 Comment(0)
P
8

There may be a more elegant solution, but you can achieve this by moving the restriction on the type to the method, rather than the type declaration:

trait Foo {
  type Bar
  val lowerBound: Bar
  val upperBound: Bar
  def foo(bar: Bar)(implicit ev: Bar => Ordered[Bar]) = {
    bar >= lowerBound && bar <= upperBound
  }
}

Then your FooImpl works as you have it:

class FooImpl extends Foo {
  type Bar = Int
  val lowerBound = 0
  val upperBound = 5
}

From the REPL:

scala> new FooImpl()
res0: FooImpl = FooImpl@2dbbec72

scala> res0.foo(3)
res1: Boolean = true

scala> res0.foo(7)
res2: Boolean = false

The disadvantage here is that the trait can be extended with unordered types (although foo can't be called in that case):

class A // not Ordered

class BrokenFoo extends Foo {
  type Bar = A
  val lowerBound = new A
  val upperBound = new A
} // compiles

new BrokenFoo().foo(new A) // doesn't compile

Alternatively, you can keep the requirement at the class level (and therefore stop anyone creating a BrokenFoo) as follows, but FooImpl has to change slightly:

trait Foo {
  type Bar
  implicit val baz: Bar => Ordered[Bar]
  val lowerBound: Bar
  val upperBound: Bar
  def foo(bar: Bar) = { bar >= lowerBound && bar <= upperBound }
}

class FooImpl extends Foo {
  type Bar = Int
  val baz = implicitly[Bar => Ordered[Bar]]
  val lowerBound = 0
  val upperBound = 5
}

This problem feels like view or context bounds should be applicable, but unfortunately it doesn't seem like you can use them either in type declarations or in generic type parameters on traits.

Pedagogy answered 13/2, 2012 at 1:10 Comment(1)
Yes, if OP is willing to to turn Foo into an abstract class, the the 'Bar' type member can be done away with, the first line of Foo's declaration can be done as "abstract class Foo[Bar <% Ordered[Bar]]". That will probably work better than just enforcing that relationship on the one method.Baleful

© 2022 - 2024 — McMap. All rights reserved.