Skip to content
Kevin Peterson edited this page Aug 30, 2017 · 2 revisions

expand is a macro annotation that is used for code generation in Breeze. You don't have to understand it if you want only to use Breeze. It's useful only for contributors.

The basic idea behind @expand is you start with something like:

implicit val foo: Impl[Double, Double]  = ???

and then you replace it with

@expand
implicit def foo[@expand.args(Int, Double, Float, Long) T]: Impl[T, T]  = ???

which generates code equivalent to:

implicit def foo_Int: Impl[Int, Int] = ???
implicit def foo_Float: Impl[Float, Float] = ???
implicit def foo_Long: Impl[Long, Long] = ???
implicit def foo_Double: Impl[Double, Double] = ???

If you have more than one type parameter with expand.args, the full cross product gets generated.

There are three remaining pieces to expand: @expand.valify, @expand.sequence, and @expand.exclude. expand.valify just replaces the def with a val, which you can only do if there are no type or value arguments left after expansion. sequence is for associating values with expanded type parameters, and exclude is for omitting particular configurations from the cross product. For example:

@expand
@expand.exclude(Double, Double)
implicit def foo[@expand.args(Int, Double) T, 
                        @expand.args(Int, Double) U]
                       (implicit @expand.sequence[T](0, 0.0) zero: T): Impl[T, U]  = zero

will generate

implicit def foo_Int_Int: Impl[Int, Int] = 0
implicit def foo_Int_Double: Impl[Int, Double] = 0
implicit def foo_Double_Int: Impl[Double, Int] = 0.0

The Double,Double version is excluded, so it doesn't get generated.

sequence sees that you want to associate zero's value with T, and so zero is replaced with 0 if T is Int and 0.0 if T is Double. Note that it is the ordering that matters. If I had written @expand.sequence[T](0.0, 0) zero, then zero would be 0.0 if T is Int, and 0 if T is Double.

One last trick is that if a sequenced value is used as a function, then the corresponding function is inlined. So...

@expand
def bar[@expand.args(Int) T](@expand.sequence[T]({_ + _}) op: (T,T)=>T) = {
   op(3,4)
}

is

def bar_Int = 3 + 4

I should note that the declared type of an @sequenced parameter does not matter at all. I could have declared op's type to be BLAH, and everything would have worked fine. I usually declare it to be something reasonable in code (like in the examples), as a kind of skeuomorphism.

expand and @specialized

As for the relationship between @expand and @specialized: They overlap but they both can coexist. @expand tries to do much less than @specialized, which is to say, expand doesn't try to make the specialized classes inherit from the non-specialized; it just does name mangling and code generation. It also (currently) only works on defs. I'll probably extend it to classes at some point.

@expand-ing arity

Another future direction for expand or an expand-like macro is to make it generate classes and methods for variable arity's (e.g. things like TupleN)