Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

companion object to a private class: why isn't it valid?

Tags:

i needed two instances that has access to each other privates. i naturaly thought of a companion object that grants access to a one and only instance of it's companion class. the class itself i made private, so users cannot just create instances using new.

object A {
    def apply = dual
    lazy val dual = new A
}

private class A {
    //some irrelevant logic...
}

this code does not compile. i get: class A escapes its defining scope as part of type A error, which i don't really understand. my current workaround was to define a trait with every method declaration the class should have and make class A extend that trait, while dual is of the trait type, and not class A type.

what's the theoretic problem i'm missing here? why is this forbiden?

like image 831
gilad hoch Avatar asked Jan 14 '13 14:01

gilad hoch


People also ask

What is the point of a companion object?

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.

Can case class have companion object?

When you want to define some functionality related to a case class you have two possible ways to do this. The first one is to create functions directly in the case class. Note that in order to define a companion object for a class you have to set the same names for them and declare them in the same file.

Can object extend a class in Scala?

An object can extend another class, making its fields and methods available in a global instance. The reverse is not true, however, because an object cannot itself be extended.

Can object extend trait Scala?

Unlike a class, Scala traits cannot be instantiated and have no arguments or parameters. However, you can inherit (extend) them using classes and objects.


2 Answers

Paolo's solution is good (+1), but he didn't explain the error message, so let me try that. The problem stems from the fact that every method needs a return type. Your original definition of apply and dual returned an object of class A, thus the implicit return type of both was A. That implies that A must be visible to clients - how else could they call the function or access the val? Moreover, as both - and their parent object too - are public, they are globally visible. However, you declared A private which means it must not be visible outside its package. So there is a conflict which can't be resolved by the compiler.

The general rule is that all parameter and return type of functions / members must have (at least) the same scope of visibility as the referring member itself*. Thus one trivial way to solve this problem would be to reduce the visibility of apply and dual to private. This would satisfy the compiler, but not you :-)

Your solution gets around the problem by changing the static return type to a public trait, which thus has the same visibility as the members referring to it. The dynamic type of the returned object is still class A, however, this need not be visible to clients. This is a classic example of the principle "program to interfaces, not implementations".

Note that to apply this principle to the full extent, one could turn class A into a private inner class of object A, thus making it innaccessible even for other classes within the same package:

trait A {
    //...
}

object A {
    def apply: A = dual
    lazy val dual: A = new AImpl

    private class AImpl extends A {
        //some irrelevant logic...
    }

}

* To be pedantic, the enclosing class / object may reduce the visibility of its members, like here:

private class Holder {
  def member = new Hidden
}

private class Hidden

where member is public but its enclosing class is private, effectively hiding its members from the external world. So the compiler emits no complaints here.

like image 96
Péter Török Avatar answered Oct 01 '22 02:10

Péter Török


I think you don't want a private class, but a class with a private constructor.

class A private() 
object A {
    def apply = dual
    lazy val dual = new A
}

Now your class is "visible" to outside code, but only your companion object can create instances of it.

like image 33
Paolo Falabella Avatar answered Oct 01 '22 02:10

Paolo Falabella