Scala singleton factories and class constants
Asked Answered
P

2

1

OK, in the question about 'Class Variables as constants', I get the fact that the constants are not available until after the 'official' constructor has been run (i.e. until you have an instance). BUT, what if I need the companion singleton to make calls on the class:

object thing {
    val someConst = 42
    def apply(x: Int) = new thing(x)
}

class thing(x: Int) {
    import thing.someConst
    val field = x * someConst
    override def toString = "val: " + field
}

If I create companion object first, the 'new thing(x)' (in the companion) causes an error. However, if I define the class first, the 'x * someConst' (in the class definition) causes an error.

I also tried placing the class definition inside the singleton.

object thing {
    var someConst = 42

    def apply(x: Int) = new thing(x)

    class thing(x: Int) {
        val field = x * someConst
        override def toString = "val: " + field
    }
}

However, doing this gives me a 'thing.thing' type object

val t = thing(2)

results in

t: thing.thing = val: 84

The only useful solution I've come up with is to create an abstract class, a companion and an inner class (which extends the abstract class):

abstract class thing

object thing {
    val someConst = 42
    def apply(x: Int) = new privThing(x)

    class privThing(x: Int) extends thing {
        val field = x * someConst
        override def toString = "val: " + field
    }
}

val t1 = thing(2)
val tArr: Array[thing] = Array(t1)

OK, 't1' still has type of 'thing.privThing', but it can now be treated as a 'thing'.

However, it's still not an elegant solution, can anyone tell me a better way to do this?

PS. I should mention, I'm using Scala 2.8.1 on Windows 7

Polish answered 15/12, 2010 at 14:46 Comment(0)
M
12

First, the error you're seeing (you didn't tell me what it is) isn't a runtime error. The thing constructor isn't called when the thing singleton is initialized -- it's called later when you call thing.apply, so there's no circular reference at runtime.

Second, you do have a circular reference at compile time, but that doesn't cause a problem when you're compiling a scala file that you've saved on disk -- the compiler can even resolve circular references between different files. (I tested. I put your original code in a file and compiled it, and it worked fine.)

Your real problem comes from trying to run this code in the Scala REPL. Here's what the REPL does and why this is a problem in the REPL. You're entering object thing and as soon as you finish, the REPL tries to compile it, because it's reached the end of a coherent chunk of code. (Semicolon inference was able to infer a semicolon at the end of the object, and that meant the compiler could get to work on that chunk of code.) But since you haven't defined class thing it can't compile it. You have the same problem when you reverse the definitions of class thing and object thing.

The solution is to nest both class thing and object thing inside some outer object. This will defer compilation until that outer object is complete, at which point the compiler will see the definitions of class thing and object thing at the same time. You can run import thingwrapper._ right after that to make class thing and object thing available in global scope for the REPL. When you're ready to integrate your code into a file somewhere, just ditch the outer class thingwrapper.

object thingwrapper{
   //you only need a wrapper object in the REPL
   object thing {
       val someConst = 42
       def apply(x: Int) = new thing(x)
   }   

   class thing(x: Int) {
       import thing.someConst
       val field = x * someConst
       override def toString = "val: " + field
   }   
}
Multitudinous answered 15/12, 2010 at 15:33 Comment(1)
Thanks. Yes, I was trying my examples in the Scala runtime environment. When I saved to disk and compiled it worked fine. Looks like I put a lot of thought into a non-issue, doh!Polish
U
0

Scala 2.12 or more could benefit for sip 23 which just (August 2016) pass to the next iteration (considered a “good idea”, but is a work-in-process)

Literal-based singleton types

Singleton types bridge the gap between the value level and the type level and hence allow the exploration in Scala of techniques which would typically only be available in languages with support for full-spectrum dependent types.

Scala’s type system can model constants (e.g. 42, "foo", classOf[String]).
These are inferred in cases like object O { final val x = 42 }. They are used to denote and propagate compile time constants (See 6.24 Constant Expressions and discussion of “constant value definition” in 4.1 Value Declarations and Definitions).
However, there is no surface syntax to express such types. This makes people who need them, create macros that would provide workarounds to do just that (e.g. shapeless).
This can be changed in a relatively simple way, as the whole machinery to enable this is already present in the scala compiler.

type _42 = 42.type
type Unt = ().type
type _1 = 1 // .type is optional for literals
final val x = 1
type one = x.type // … but mandatory for identifiers
Universe answered 21/8, 2016 at 14:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.