Scala:理解参数化多态性

4
什么是两者之间的区别?
def drop1[A](l: List[A]) = l.tail

并且

def drop1(l: List[Int]) = l.tail

只要使用看起来像这样的东西

drop1(List(1,2,3))

在什么情况下应该使用其中一个,而不是另一个?虽然我可以理解第二个例子,但我并不真正理解第一个的目的。

4个回答

7

其实很简单。你的第一个例子涉及到“泛型”这个概念。

“泛型”的目标很简单,就是让某些方法成为“通用的”,例如不依赖于具体类型。

我们来看一个简单的例子。假设我想写一个针对Listdrop1方法。

我可以为每种类型都写一个方法(费时、重复):

def drop1(l: List[Int]): List[Int] = l.tail // this only works for Int

def drop1(l: List[String]): List[String] = l.tail // this only works for String 

你可以看到,你必须为每种类型编写以上内容。为了克服这个问题,你需要使用泛型:

您可以看到,对于每个单独的类型,您都需要编写上述代码。为了解决这个问题,您可以使用泛型:

def drop1[A](l: List[A]): List[A] = l.tail // this works for any given type.

这段代码的含义是:无论列表中包含什么类型,都给我它的尾部。 为了避免为数量几乎无限的类型编写成千上万个drop1变体,我只需要编写一个。

在Scala中,你可以使用以下代码实现:

implicit class ListOps[A](val l: List[A]) extends AnyVal {
   def drop1: List[A] = l match {
     case head :: tail => tail
     case Nil => Nil
   }
}
// you can now have
List(1, 2, 3).drop1

通常情况下,重命名已知的库方法是不好的做法。tail操作是不安全的,而drop操作是安全的。这样做只会引起混淆,因为默认情况下已有一个drop方法。请勿更改现有的方法名。
List(1, 2, 3) drop 1

谢谢你的回答。为什么在我第一个例子中需要第一个 [A]?这只是语法要求吗? - Caballero
2
是的,编译器可以区分你引入的类型和已经定义的A类型。为什么要这样做呢?为了防止你犯错并使你的意图明确化。 - om-nom-nom
谢谢。事实上,正是在阅读本教程时我提出了这个问题。 - Caballero
更深层次的原因(在某些人看来,“参数化多态性”中“参数化”一词的来源)被称为参数性。具体而言,您专门针对Intdrop1对其可以执行的操作有更少的保证。例如,它可以查看第五个整数,如果它等于74,则可以删除五个元素而不仅仅是一个。理论上,参数化版本不知道元素的任何信息,只能在列表的结构上进行操作。当然,Scala暴露了JVM的缺陷,如.isInstanceOf,以解决这个问题,但至少在理论上,参数化的drop1更加可靠。 - Mysterious Dan

3
简而言之,有些操作不依赖于特定类型并且可以被抽象化。计算苹果和计算橙子本质上是相同的操作。如果你要重复使用算法,将一些类型抽象化而不是编写新代码是更为聪明的做法。
def countOranges(xs: List[Orange]) = { some code } 
def countApples(xs: List[Apple]) = { the very same code }

0

可能有点晚,如果你对Java中的泛型有所了解,那么可以在这里进行类比:

Java --> 泛型类的对象可以作为方法参数传递。

class Test<T> {
    // An object of type T is declared
    T obj;
    Test(T obj) {  this.obj = obj;  }  // constructor
    // some other methods in class 
}

Test<String> testobj = new Test<String>();

public void function(testobj){ 
// do something with testobj 
}

Scala --> 在 Scala 中,泛型参数化函数的工作方式与此类似。这里,[A] 定义了 Scala 中的通用类型。

def drop1[A](l: List[A]) = l.tail

以上函数的使用方法:

scala>drop1(List(1,2,3))   // output List(2, 3)
scala>drop1(List(1.0,2.0,3.0)) // output List(2.0, 3.0)
scala>drop1(List('a','b','c'))  // output List(b, c)

说明:- 只需传递任何类型的列表,它就能像魅力一样工作。 语法如下:

def function[Type](param:Type):Type = {do something}

0
//snippet to explain scala parametric polymorphism

object MethodParametricPolymorphism {
  //parametric polymorphism is similar/same as of java generics
  def countOranges(oranges : List[Orange]) = oranges.size;
  def countApples(apples : List[Apple]) = apples.size
  //if algo is same then paramatric polymorphism can be used
  def count[A](items : List[A]) = items.size

  val oranges : List[Orange] =  List( new Orange(1.1))
  val apples : List[Apple] =  List( new Apple(2.1), new Apple(2.2))

  countOranges(oranges);
  countApples(apples);
  //using polymorphic typed method
  count(oranges);
  count(apples);

  case class Orange ( weight: Double)
  case class Apple ( weight: Double)
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接