1

I'm trying to create a singleton object for every natural number

Objects cannot take parameters, so I'm trying to use classes. I have some logic to make sure only one object is created per number. Here's what I have so far, it works as I would expect:

class B(val n: Int)

object A extends (Int => B) {
    val map = collection.mutable.Map[Int,B]()
    def apply(n: Int) : B = {
        if (!map.contains(n))map.put(n,new B(n))
        map(n)
    }
}

I can then use A(3) as a singleton

Ideally, I would like only A to be able to create B objects - e.g. I want to forbid the creation of B objects outside the scope of A. So, I tried making B an inner class of A as follows:

object A extends (Int => A.B) {
    val map = collection.mutable.Map[Int,B]()
    def apply(n: Int) : B = {
        if (!map.contains(n))map.put(n,new B(n))
        map(n)
    }
    class B private[A] (val n: Int)
}

But now I get this error: error: illegal cyclic reference involving object A

Hackingly replacing A.B with Any, everything works as I want it to again

Is there a nice way of doing what I'm trying to do?

Edit

The answer from Paul Renauld solves the issue of limit scope quite nicely! Thanks

I'm now asking rather for an explanation of the illegal cyclic reference. I tried a quick google search but can't seem to find good resources to learn more about this error. What specifically does it mean? Why does it come up; in what circumstances?

I imagine this has to do with how Scala is parsed / interpreted / compiled - I know very little about this. Maybe the answer to my question is a link to a resource about the Scala compiler or some reference page?

5
  • 2
    Why do you need these objects? Maybe there is a different way to solve your problem? Commented Aug 1, 2019 at 18:33
  • 1
    Just FYI: Instead of the if check, you could use getOrElseUpdate(). Commented Aug 1, 2019 at 18:56
  • @jwvh thanks for the tip! My apply function is now inline, it brings me great joy! Commented Aug 1, 2019 at 20:05
  • @KrzysztofAtłasik I'm trying to make a library to model group theory. I have a trait Group[T] which extends Set[A] and supports operations like inverse(x:T):T and id:T. I've implemented the group of integers as a subclass of Group[Int]. I'm now trying to implement the Dihedral group(s) as a subclass of Group[D] where D would be type represent e or r^3s. Since there is a dihedral group for every even positive integer, this is why I'm looking for singletons with parameters. I'm doing this for fun, and to push my understanding of the Scala language Commented Aug 1, 2019 at 20:16
  • For the cyclic reference, I don't know the exact logic behind it, but my guess is: The type of Object A tries to be created, but this extends a function of Int to A.B, so to get the function type, we need to access the type of B, but B is an inner class of A, so we need to create A first, and we get our cycle. Commented Aug 2, 2019 at 13:45

2 Answers 2

3

You don't need to have B as an inner class of A to make the constructor only accessible by A. You could have a package with only A and B and limit the scope of the constructor to this package.

Another solution is to not have A extending (Int => A.B):

object A {
  val map = collection.mutable.Map[Int,B]()
  def apply(n: Int) : B = {
    if (!map.contains(n))map.put(n,new B(n))
    map(n)
  }
  class B private[A] (val n: Int)
}

This works perfectly fine too.

Sign up to request clarification or add additional context in comments.

Comments

0

I'm now asking rather for an explanation of the illegal cyclic reference.

You want to instantiate A. A extends Int ⇒ A.B. So, in order to get access to the B that is part of A's inheritance chain, you need to instantiate A. A extends Int ⇒ A.B. So, in order to get access to the B that is part of A's inheritance chain, you need to instantiate A. A extends Int ⇒ A.B. So, in order to get access to the B that is part of A's inheritance chain, you need to instantiate A. A extends Int ⇒ A.B. So, in order to get access to the B that is part of A's inheritance chain, you need to instantiate A. And so on …

1 Comment

That makes sense, thanks! Would it be reasonable to want Scala to be able to do this? It seems to me as though this cyclical reference should be able to be resolved. How would the Scala compiler need to change for this? My reasoning is instantiation could be broken up: A is partially instantiated (as a promise), then A.B would make sense, which then allows A to be instantiated in full. As much as what I'm trying to do here is indeed cyclical, I'm not convinced that it should be illegal

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.