Skip to content

Instantly share code, notes, and snippets.

@paulp
Created February 1, 2013 17:57
Show Gist options
  • Save paulp/4692921 to your computer and use it in GitHub Desktop.
Save paulp/4692921 to your computer and use it in GitHub Desktop.
statically typed companion objects
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 **/
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]
}
}
@milessabin
Copy link

I'm not really seeing the payoff here: if I statically know that x in f1 is of type Foo, then I can mention object Foo directly rather than having to go via x.companion. Is there anything I can do with .companion that I couldn't do already?

Copy link

ghost commented Feb 1, 2013

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

@milessabin
Copy link

@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.

@jrudolph
Copy link

jrudolph commented Feb 2, 2013

@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?

@milessabin
Copy link

@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