【问题标题】:Scala singleton factories and class constantsScala 单例工厂和类常量
【发布时间】:2011-05-25 23:12:46
【问题描述】:

好的,在关于'Class Variables as constants' 的问题中,我知道常量在“官方”构造函数运行之后(即直到你有一个实例)才可用。但是,如果我需要同伴单例来调用班级怎么办:

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
}

如果我首先创建伴随对象,则“新事物(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',但现在可以将其视为 'thing'。

但是,这仍然不是一个优雅的解决方案,谁能告诉我一个更好的方法来做到这一点?

附言。我应该提一下,我在 Windows 7 上使用 Scala 2.8.1

【问题讨论】:

    标签: scala syntax scala-2.8


    【解决方案1】:

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

    基于文字的单例类型

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

    Scala 的类型系统可以对常量建模(例如 42、"foo"、classOf[String])。
    这些是在object O { final val x = 42 } 等情况下推断出来的。它们用于表示和传播编译时间常量(参见6.24 Constant Expressions 和4.1 Value Declarations and Definitions 中“常量值定义”的讨论)。
    但是,没有表达这种类型的表面语法。这使得需要它们的人创建宏来提供解决方法来做到这一点(例如shapeless)。
    这可以通过相对简单的方式进行更改,因为启用此功能的整个机制已经存在于 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
    

    【讨论】:

      【解决方案2】:

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

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

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

      解决方案是将class thing 和object thing 都嵌套在某个外部对象中。这将推迟编译直到外部对象完成,此时编译器将同时看到class thing 和object thing 的定义。您可以在此之后立即运行 import thingwrapper._ 以使 class thing 和 object 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
         }   
      }
      

      【讨论】:

      • 谢谢。是的,我在 Scala 运行时环境中尝试了我的示例。当我保存到磁盘并编译它工作正常。看起来我在一个非问题上花了很多心思,doh!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多