Suppose that I have some trait Foo, for example:
trait Foo {
def bar: List[Int]
def baz(i: Int): Unit
}
I want to enforce at compile time that all inputs passed to baz have been previously produced by bar. For example, if this is an implementation of Foo:
object A extends Foo {
def bar = List(2, 3, 5, 7)
def baz(i: Int): Unit = {
if (bar contains i) println("ok")
else println("not good")
}
}
then I want to enforce that only single-digit primes can be passed to baz. This obviously doesn't work if the input type of baz is known to be Int, because it allows me to instantiate all kinds of integers that are not prime and not between 0 and 9:
val okValues: List[Int] = A.bar
A.baz(okValues(1)) // ok
A.baz(3) // ok (but dangerous! `3` appeared out of nowhere!)
A.baz(42) // not good
How can I enforce that only the values previously produced by bar can be passed to baz?
What doesn't work
Converting Int to a type member of Foo doesn't help, because it's instantiated to the concrete type Int in the implementation A of Foo:
trait Foo {
type T
def bar: List[T]
def baz(t: T): Unit
}
object A extends Foo {
type T = Int
def bar = List(2, 3, 4, 5)
def baz(i: Int): Unit = {
if (bar contains i) println("ok")
else println("not good")
}
}
A.baz(42) // not good
Here is one solution that relies on replacing a concrete type
Intby an abstract type memberT, and then simply not exposing the concrete implementation ofTbyInt:Intby an abstract type memberTbarandbazand the typeTinto an inner traitYFooFoo, provide a method that producesYFoo, but does not expose whatTis.In code:
Now all the dangerous / invalid stuff doesn't even compile. The only values that you can pass to
bazare those from the "list of certified values" produced bybar.