Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Scala's singleton-object types?

I'm writing a class which serves as base class for a series of singleton objects. In each singleton objects, there will be vals representing certain properties, and I want to write a method which, for each singleton object, only accepts objects created by it.

So I have the following:

class Obj[M <: Maker]

class Maker {
  implicit val me: this.type = this
  def make[M <: Maker](implicit maker: M) = new Obj[M]
  def accept(obj: Obj[this.type]) = {...}
}

So far, so good. Then I want to declare one of these singleton objects:

object M extends Maker {
  val a = make
}

But then, if I try this:

M.accept(M.a)

then I get a compile-time error:

type mismatch; found : com.test.Obj[object com.test.M] required: com.test.Obj[com.test.M.type]

My questions:

  1. What's the type object com.test.M, and how is it different from com.test.M.type?
  2. How can I do this in a smarter way?
like image 353
Jean-Philippe Pellet Avatar asked Nov 30 '10 15:11

Jean-Philippe Pellet


People also ask

Why do we use singleton object in Scala?

Instead of static keyword Scala has singleton object. A Singleton object is an object which defines a single object of a class. A singleton object provides an entry point to your program execution. If you do not create a singleton object in your program, then your code compile successfully but does not give output.

How can a singleton object be made executable in Scala?

Scala Singleton Object Singleton object is an object which is declared by using object keyword instead by class. No object is required to call methods declared inside singleton object. In scala, there is no static concept. So scala creates a singleton object to provide entry point for your program execution.

What is the difference between singleton object and companion object?

A singleton object named the same as a class is called a companion object. Also a companion object must be defined inside the same source file as the class.

What is the use of companion objects?

A companion object is an object that's declared in the same file as a class , and has the same name as the class. A companion object and its class can access each other's private members. A companion object's apply method lets you create new instances of a class without using the new keyword.


4 Answers

Get with the times, my good man! I fixed this over 24 hours ago. Next I expect to see velociraptors chasing dodos, furiously cracking their buggy whips while looking up stock quotes on their pointcast screensavers.

The commit in question is: http://lampsvn.epfl.ch/trac/scala/changeset/23622

// 1130.scala
class Obj[M <: Maker]

class Maker {
  implicit val me: this.type = this
  def make[M <: Maker](implicit maker: M) = new Obj[M]
  def accept(obj: Obj[this.type]) = ()
}

object M extends Maker {
  val a = make
}

object Test {
  def main(args: Array[String]): Unit = {
    M.accept(M.a)
  }
}

// too old
% /scala/inst/scala-2.9.0.r23619/bin/scalac ./1130.scala 
./1130.scala:15: error: type mismatch;
 found   : Obj[object M]
 required: Obj[M.type]
    M.accept(M.a)
               ^
one error found

// fresh enough
% /scala/inst/scala-2.9.0.r23624/bin/scalac ./1130.scala 
%
like image 130
psp Avatar answered Oct 29 '22 13:10

psp


Use this.type instead of M. This simplified example should work:

class Obj[M <: Maker]

class Maker {
  def make() = new Obj[this.type]
  def accept(obj: Obj[this.type]) = println(obj)
}

object M extends Maker

object N extends Maker

M.accept(M.make()) //works!
M.accept(N.make()) //error! type mismatch!
like image 23
Michel Krämer Avatar answered Oct 29 '22 14:10

Michel Krämer


Your first question, "What's the type object com.test.M, and how is it different from com.test.M.type?", still hasn't been answered. I haven't found it documented in the spec, but it seems that the object M type is the internal type representing the class that is implicitly created when you define an object M. Of course, M is the only instance of that class so one would expect the object M type to be equivalent to M.type, but the compiler apparently does not see it that way.

The problem you're running into, as @retronym explained, is that the singleton type M.type is not inferred for the type parameter when you invoke your make method. This is for the same reason that String is inferred rather than v.type in the session below:

scala> val v = "asdf"                      
v: java.lang.String = asdf

scala> identity(v)
res0: java.lang.String  = asdf

where identity is defined as

def identity[T](v: T) = v
like image 38
Aaron Novstrup Avatar answered Oct 29 '22 14:10

Aaron Novstrup


This works:

class Obj[M <: Maker]

class Maker {
  implicit val me: this.type = this
  def make[M <: Maker](implicit maker: M) = new Obj[M]
  def accept(obj: Obj[this.type]) = ()
}

object M extends Maker {
  val a = make[M.type]
}

M.accept(M.a)

The secret "sauce" is using make[M.type] inside the singleton object.

@retronym deserves the credit for explaining this: How to correctly type-annotate this HList?

like image 24
IttayD Avatar answered Oct 29 '22 14:10

IttayD