Scala 单例工厂和类常量

发布于 2024-10-07 22:16:26 字数 1401 浏览 5 评论 0原文

好的,在关于“类变量作为常量”的问题中,我事实是,这些常量只有在“官方”构造函数运行之后才可用(即直到您有一个实例)。但是,如果我需要伴随单例来调用该类怎么办:

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
}

如果我首先创建伴随对象,则“new thing(x)”(在伴随对象中)会导致错误。但是,如果我首先定义类,则“x * someConst”(在类定义中)会导致错误。

我还尝试将类定义放在单例中。

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
    }
}

然而,这样做会给我一个“thing.thing”类型的对象

val t = thing(2)

结果,

t: thing.thing = val: 84

我想出的唯一有用的解决方案是创建一个抽象类,一个同伴和一个内部类(它扩展了抽象类):

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)

好的,' t1' 仍然具有“thing.privThing”类型,但现在可以将其视为“事物”。

然而,这仍然不是一个优雅的解决方案,有人能告诉我更好的方法吗?

附言。我应该提到,我在 Windows 7 上使用 Scala 2.8.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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

十六岁半 2024-10-14 22:16:26

首先,您看到的错误(您没有告诉我它是什么)不是运行时错误。当 thing 单例初始化时,不会调用 thing 构造函数 - 它会在稍后调用 thing.apply 时调用,因此不存在循环运行时参考。

其次,在编译时确实存在循环引用,但是当您编译保存在磁盘上的 scala 文件时,这不会导致问题 - 编译器甚至可以解决不同文件之间的循环引用。 (我测试过。我把你的原始代码放在一个文件中并编译它,它工作得很好。)

你真正的问题来自于尝试在 Scala REPL 中运行这段代码。 这是 REPL 的作用以及为什么这是 REPL 中的问题。您正在输入对象事物,一旦完成,REPL 就会尝试编译它,因为它已经到达了一个连贯的代码块的末尾。 (分号推断能够推断出对象末尾的分号,这意味着编译器可以开始处理该代码块。)但是由于您还没有定义 class thing 它可以不编译它。当你颠倒类事物对象事物的定义时,你也会遇到同样的问题。

解决方案是将类事物对象事物嵌套在某个外部对象内。这将推迟编译,直到外部对象完成,此时编译器将同时看到class thingobject thing的定义。您可以在此之后立即运行 import thingwrapper._ 以使 class thingobject thing 在 REPL 的全局范围内可用。 当您准备好将代码集成到某个文件中时,只需放弃外部类 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
   }   
}

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
   }   
}
所有深爱都是秘密 2024-10-14 22:16:26

Scala 2.12 或更高版本可能会受益于 sip 23< /a> 刚刚(2016 年 8 月)传递到下一个迭代(被认为是一个“好主意”,但仍在进行中)

基于文字的单例类型

单例类型弥合了值级别和类型级别之间的差距,因此允许在 Scala 中探索通常仅在支持全谱依赖类型的语言中可用的技术。

Scala 的类型系统可以对常量建模(例如 42"foo"classOf[String])。
这些是在 object O { Final val x = 42 } 等情况下推断出来的。它们用于表示和传播编译时常量(请参阅 6.24 常量表达式以及4.1 值声明和定义)。
但是,没有表面语法来表达此类类型。这使得需要它们的人创建宏来提供解决方法来做到这一点(例如 无形状)。
这可以通过相对简单的方式进行更改,因为实现此功能的整个机制已经存在于 scala 编译器中。

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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文