Scala: understanding parametric polymorphism

2020-06-22 05:28发布

问题:

What is the difference between

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

and

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

provided the usage looks something like

drop1(List(1,2,3))

?

When should one or the other be used and why? Whereas I can understand the second example, I don't really understand the purpose of the first one.

回答1:

It's very simple really. Your first example refers to the concept of generics.

Generics have a simple goal, to make certain methods generic, e.g non-type dependant.

Lets look at this trivial example. Say I want to write a drop1 method for List.

I can either write one for every single type:(slow, repetitive):

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 

You can see how you'd have to write the above for every single type. To overcome this, you have generics:

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

Which essentially says: Whatever the type contained in the List is, give me the tail. Instead of writing thousands of variants of drop1 for the virtually infinite number of types, I only need to write one.

Now in Scala, your implementation is best done with:

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

It is also generally a bad idea to rename well known library methods. A tail operation is unsafe and a drop is safe. All you are causing is confusion, since there is a default drop method.

List(1, 2, 3) drop 1


回答2:

In short -- some operations do not depend on a specific type and can be abstracted. Counting apples and counting oranges is essentially the same operation. If you're going to reuse algorithm, it is way smarter to abstract some types away instead of writing

def countOranges(xs: List[Orange]) = { some code } 
def countApples(xs: List[Apple]) = { the very same code }


回答3:

My be little late , if you have an idea about generics in Java then analogy can be made here:-

Java --> generic class's object can be passed in method argument.

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 --> Same way generic parametric functions work in scala. Here, [A] defines the generic Type in scala

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

usage of above function:-

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)

Explanation:- Just pass any type of List it works like a charm.The syntax is like below:-

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


回答4:

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