Created
February 1, 2013 17:57
-
-
Save paulp/4692921 to your computer and use it in GitHub Desktop.
statically typed companion objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package s | |
object Test { | |
// Observe that x.companion is statically typed such that foo is callable | |
def f1() = { | |
val x = new Foo | |
println(x) // Foo instance | |
println(x.companion) // Foo companion | |
println(x.companion.foo) // I'm foo! | |
} | |
// Observe that we now have a static handle on the case class factory | |
def f2() = { | |
val y = Bippy(5) | |
println(y) // Bippy(5) | |
println(y.companion) // Bippy | |
println(y.companion.apply(10)) // Bippy(10) | |
} | |
def f3() = { | |
val y = new Bar | |
/**** | |
println(y.companion) // does not compile | |
b.scala:12: error: Instance of s.Bar has no companion object | |
println(y.companion) | |
^ | |
one error found | |
****/ | |
() | |
} | |
def main(args: Array[String]): Unit = { | |
f1() | |
f2() | |
} | |
} | |
class Foo { | |
override def toString = "Foo instance" | |
} | |
object Foo { | |
def foo = "I'm foo!" | |
override def toString = "Foo companion" | |
} | |
case class Bippy(x: Int) | |
class Bar { | |
override def toString = "Bar instance" | |
} | |
/** No object Bar **/ | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.language.experimental.macros | |
import scala.language.implicitConversions | |
import scala.reflect.macros.Context | |
package s { | |
object typeOps { | |
def companionImpl[T](c: Context) = { | |
import c.universe._ | |
val CompanionOpsClass = typeOf[CompanionOps[_]].typeSymbol | |
val clazzType = c.prefix.actualType match { | |
case TypeRef(_, CompanionOpsClass, arg :: Nil) => arg | |
case tp => c.abort(c.enclosingPosition, s"Unexpected prefix: $tp/${tp.getClass}") | |
} | |
val companion = clazzType.typeSymbol.companionSymbol match { | |
case NoSymbol => c.abort(c.enclosingPosition, s"Instance of $clazzType has no companion object") | |
case sym => sym | |
} | |
def make[U: c.WeakTypeTag] = c.Expr[U](treeBuild.mkAttributedRef(companion)) | |
make(c.WeakTypeTag(companion.typeSignature)) | |
} | |
} | |
} | |
package object s { | |
implicit class CompanionOps[T](val clazz: T) { | |
def companion: AnyRef = macro typeOps.companionImpl[T] | |
} | |
} |
@rmellgren That's just the problem: if T is a type variable then you can't go to the companion in the way Paul is trying to do it. You could sprinkle implicit witnesses to enable that, but you could have done that anyway without the macro.
@milessabin So you would like to infer the type of the companion from the type of a case class instance? E.g. to put a constraint on which kind of case classes to accept based on the type of its companion.
Couldn't you let a macro generate an implicit witness to do that?
@jrudolph yes, something along those lines could be very useful.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
well if you receive an instance of some type T that is statically known, you could then go from there to the companion. I can think of several patterns that have explicit companion references that this would make somewhat simpler