Here is my problem: I have a large sequence of objects on which I want to apply procedures (which I call "compilers"), if some predicate apply. For clarity, I want to separate the predicate function from the procedure; however in many case the predicate may be quite complex, and builds information that I would like to reuse in my latter procedure function.
I define a compiler as follows:
trait Compiler[A] {
def mtch(o: SourceObject): Option[A]
def proceed(o: SourceObject, data: A): Unit
}
And how compilers are called:
val compilers: Seq[Compiler[_]]
val objects: Seq[SourceObject]
for (o <- objects; c <- compilers; data <- c.mtch(o)) {
c.proceed(o, data)
}
That is, if the mtch
function returns Some(data)
, then the proceed
method is called, with data
attached. However, I cannot compile this, as I do not know the type of data when I manage my compilers.
Moreover, I have cases where I do not actually need any data. In my current state, I have the matcher return Some(null)
, which stinks.
Go with path-dependent types instead. Replace
trait Compiler[A] {
def mtch(o: SourceObject): Option[A]
def proceed(o: SourceObject, data: A): Unit
}
with
trait Compiler {
type A
def mtch(o: SourceObject): Option[A]
def proceed(o: SourceObject, data: A): Unit
}
and everything will work.
The trick here is that the type of data
in your for-comprehension becomes c.A
, which is the type c.proceed
expects as its second parameter.
As for null
, make compiler which don't need to pass parameters type A = Unit
, so you return Some(())
if it should proceed.