【问题标题】:How to create a Scala function that can parametrically create instances of sub-types of some type如何创建一个 Scala 函数,该函数可以参数化地创建某种类型的子类型的实例
【发布时间】:2018-11-01 15:48:58
【问题描述】:

抱歉,我对 Scala 不是很熟悉,但我很好奇这是否可行,但我一直无法弄清楚如何。

基本上,我想创建一些可以生成随机数据样本(在本例中为网格)的便利初始化程序。网格将始终填充特定类型的实例(在本例中为Location)。但在不同的情况下,我可能希望网格填充Location 的不同子类型,例如FarmCity

在 Python 中,这很简单:

def fillCollection(klass, size):
    return [klass() for _ in range(size)]

class City: pass

cities = fillCollection(City, 10)

我尝试在 Scala 中做类似的事情,但它不起作用:

def fillGrid[T <: Location](size): Vector[T] = {
    Vector.fill[T](size, size) {
        T()
    }
}

编译器只是说“未找到:值 T”

那么,是否可以在 Scala 中近似上述 Python 代码?如果没有,处理这种情况的推荐方法是什么?我可以为每个子类型编写一个初始化程序,但在我的真实代码中,它们之间有相当多的样板代码重叠,所以如果可能的话,我想分享代码。

到目前为止,我想出的最佳解决方法是将闭包传递给初始化程序(这似乎是 Vectors 上的 fill 方法已经起作用的方式),例如:

  def fillGrid[T <: Location](withElem: => T, size: Int = 100): Vector[T] = {
    Vector.fill[T](n1 = size, n2 = size)(withElem)
  }

这不是很大的不便,但它让我很好奇为什么 Scala 不支持“更简单”的 Python 风格的构造(如果它实际上不支持的话)。我有点明白为什么有一个“完全通用”的初始化程序会导致麻烦,但在这种情况下,我看不出一般初始化所有已知是给定子类型的实例的危害是什么父类型。

【问题讨论】:

    标签: scala


    【解决方案1】:

    你是对的,你所拥有的可能是最简单的选择。 Scala 不能以 python 方式做事的原因是类型系统要强大得多,它必须与类型擦除抗衡。 Scala 不能在编译时保证 Location 的任何子类都有特定的构造函数,它只允许你做它可以保证符合类型的事情(除非你用反射做一些棘手的事情)。

    如果你想稍微清理一下,你可以通过使用implicits让它更像python。

    implicit def emptyFarm(): Farm = new Farm
    implicit def emptyCity(): City = new City
    
    def fillGrid[T <: Location](size: Int = 100)(implicit withElem: () => T): Vector[Vector[T]] = {
      Vector.fill[T](n1 = size, n2 = size)(withElem())
    }
    
    fillGrid[farm](3)
    

    为了使其在库中更有用,通常将隐式放在 Location 的伴随对象中,这样它们都可以在适当的地方被纳入范围。

    sealed trait Location
    ...
    object Location
    {
      implicit def emptyFarm...
      implicit def emptyCity...
    }
    ...
    import Location._
    fillGrid[Farm](3)
    

    【讨论】:

    • 谢谢。这个答案是有道理的,隐含的似乎是一个干净的策略。我对“编译器无法确保特定的构造函数”感到好奇——例如Location 有一个零参数构造函数,Location 的子类型是否有可能 有一个零参数构造函数(即子类型能否以某种方式无法访问父母的方法?)。否则,T &lt;: Location 要求似乎应该确保存在必要的构造函数 iff `Location has it。
    • 你最大的敌人是类型擦除。基本上,JVM 会自动将传入的所有内容视为类型上限,在本例中为 Location。不幸的是,这意味着您不知道所提供的实际类,只是知道它是 Location 的某个子类型。 Scala 有一个非常好的反射库可以在你绝对需要的时候解决这个问题,但最好避免它,因为你会失去很多类型安全性。
    • 这段代码有很多问题,包括错误的返回类型(应该是Vector[Vector[T]]),将函数传递给fill而不是值(应该是withElem()),以及期望 implicit 方法与 implicit val 匹配。如果可能,最好提供可以编译和工作的示例代码。
    【解决方案2】:

    你可以使用反射来完成你想要的......

    这是一个简单的示例,仅当您的所有子类都具有零参数构造函数时才有效。

    sealed trait Location
    class Farm extends Location
    class City extends Location
    
    def fillGrid[T <: Location](size: Int)(implicit TTag: scala.reflect.ClassTag[T]): Vector[Vector[T]] = {
      val TClass = TTag.runtimeClass
      Vector.fill[T](size, size) { TClass.newInstance().asInstanceOf[T] }
    }
    

    但是,我从来都不是运行时反射的粉丝,我希望可以有另一种方式。

    【讨论】:

      【解决方案3】:

      Scala 不能直接做这种事情,因为它不是类型安全的。如果您传递一个没有零参数构造函数的类,它将不起作用。如果您尝试这样做,Python 版本会在运行时引发错误。

      关闭可能是最好的方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-09
        • 2014-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-17
        相关资源
        最近更新 更多