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