【问题标题】:How does "apply" work in an companion object (with Trait) in Scala?“应用”如何在 Scala 的伴生对象(带有 Trait)中工作?
【发布时间】:2014-09-26 14:04:06
【问题描述】:

来源:Scala MEAP v10 中的函数式编程

在下面粘贴的代码中

sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]

object List {
  def sum(ints: List[Int]): Int = ints match {
    case Nil => 0
    case Cons(x,xs) => x + sum(xs)
 }
  def product(ds: List[Double]): Double = ds match {
    case Nil => 1.0
    case Cons(0.0, _) => 0.0
    case Cons(x,xs) => x * product(xs)
  }
  def apply[A](as: A*): List[A] = {

    if (as.isEmpty) Nil
    else Cons(as.head, apply(as.tail: _*))
  }
  val example = Cons(1, Cons(2, Cons(3, Nil)))
}

Cons对象大概是由apply()构造的,但是类型签名不同,scala最终是如何组装Cons实例的。

虽然没有取消应用,但下面的代码工作得很好,将 List 分解为 Cons(head, tail)

object a{
  val x = List(1,2,3,4,5) match {
    case Cons(x, Cons(2, Cons(4, _))) => x
    case Nil => 42
    case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
    case Cons(h, t) => h + List.sum(t)
    case _ => 101
  }
}

【问题讨论】:

    标签: scala traits


    【解决方案1】:

    Cons 实例始终由 Cons' 构造函数构造。可以直接调用:

    val myList = new Cons(1, new Cons(2, Nil)) // list containing the elements 1 and 2
    

    伴生对象上的apply 方法是一种工厂方法,允许您使用更好的语法构造列表:

    val myList = List(1, 2) // expanded to List.apply(1, 2) by the compiler ; same result as above
    

    没有理由需要它们具有相同的类型签名,因为它们不需要以相同的方式调用。

    至于为什么不用定义unapply 方法就可以使用模式匹配:那是因为您将Nil 和Cons 定义为案例类/对象。案例类免费获得许多功能(由编译器生成),包括unapply 方法(但也包括equals、hashcode 和toString 例如)。


    编辑:基于 cmets 的一些精度:

    Scala 中的构造函数: 在 Scala 中,每个类都有一个默认构造函数,其参数自动作为类中的字段提供——因此默认构造函数不需要有自己的方法体。例如,以下 Scala 类:

    class Foo1(bar: Int)
    class Foo2(val bar: Int)
    

    大致相当于以下Java类:

    public class Foo1 {
        public Foo1(int bar) {
            this.bar = bar;
        }
        private int bar;
    }
    
    public class Foo2 {
        public Foo2(int bar) {
            this.bar = bar;
        }
        private int bar;
        public int getBar() {
            return bar;
        }
    }
    

    多态性: Nil 和 Cons 都扩展了 List。这意味着Cons 是一种List。因此,如果您使用val myList = new Cons(1, Nil) 创建Cons 的实例,myList 是Cons 类型的对象...但它也是:

    • List 类型
    • 类型为AnyRef,因为它是所有引用类型的根类。 Scala 中的所有类默认扩展AnyRef,所以List 扩展AnyRef(因为它没有明确的extends 子句)。
    • Any 类型,它是所有 Scala 类型的根。

    以下代码使用您的 List/Cons/Nil 实现:

    val myList = List(1, 2, 3)
      // Cons(1,Cons(2,Cons(3,Nil))) => result of the toString method generated by the compiler because it's a case class
    myList.getClass.getName
      // Cons   => this is the concrete type of myList
    myList.isInstanceOf[Cons[_]]
      // true   => myList is a Cons
    myList.isInstanceOf[Nil.type]
      // false
    myList.isInstanceOf[List[_]]
      // true   => but it's also a kind of List
    val foo: List[Int] = myList
      //        => this is allowed
    myList.isInstanceOf[AnyRef]
      // true
    myList.isInstanceOf[Any]
      // true
    
    val anotherList = List()
      // Nil
    anotherList.getClass.getName
      // Nil$   => the name is mangled with a $ sign to differentiate the 'object Nil' in case you also declare a 'class Nil'
    anotherList.isInstanceOf[List[_]]
      // true
    

    【讨论】:

    • 但是 scala 怎么知道它必须创建一个带有 (head , tail) 的列表,当该实现仅在 apply 方法中时,我确实接受关于可变参数的语法糖的提及,并且但是,取消应用案例类的方法。
    • 我不确定我是否理解您的问题?在编写MyObject(param) 时调用MyObject.apply(param) 只是编译器中实现的语言约定。 apply 方法采用 vararg 参数,在 Scala 中由 Seq 表示(而不是像 Java 中的数组) - 所以参数 as: A* 像参数 as: Seq[A] 一样使用。 apply 方法中的代码然后只是使用这个Seq 的公共方法来确定里面是什么并调用正确的构造函数;如果它构造了Cons,apply 必须递归调用自身来构造Cons 的尾部。
    • 对不起,如果不清楚,调用Cons(1, Cons(2, Cons(3, Nil))) 将返回一个List(1, 2, 3, Nil),这是怎么发生的,我假设apply 方法恰好完成了构造List 实例并返回的技巧,但是当我看到类型签名的差异时感到困惑
    • 更多case class Cons[+A](head: A, tail: List[A]) extends List[A]签名没有指定构造函数的主体,那么Scala如何创建List实例。 请原谅我的新手级别
    • 在某些时候我们都是新手,别担心。 :-) 我将编辑我的答案,cmets 中没有足够的空间。
    猜你喜欢
    • 2015-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-04
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 2010-10-11
    相关资源
    最近更新 更多