When you use subtyping, once you go "up", the only idiomatic way to go "down" is pattern matching.
For example,
val apple: Apple = new Apple()
val fruit: Fruit = apply
fruit match {
case Apple() => // ...
case _ => // ...
}
However, due to type erasure, this is not a good solution for your problem.
First solution
If you are willing to abandon subtyping altogether, you can use type classes which can solve this problem quite elegantly:
trait Foo[A, F[_]] {
def skipRoot(path: F[A]): F[A]
}
implicit def fooList[A] = new Foo[A, List] {
def skipRoot(path: List[A]): List[A] = path.tail
}
implicit def fooVector[A] = new Foo[A, Vector] {
def skipRoot(path: Vector[A]): Vector[A] = path.tail
}
def genericSkipRoot[A, F[_]](collection: F[A])(implicit ev: Foo[A, F]) =
implicitly[Foo[A, F]].skipRoot(collection)
genericSkipRoot(List(1, 2, 3)) // List(2, 3)
genericSkipRoot(Vector(1, 2, 3)) // Vector(2, 3)
Sure, you have to define explicit typeclass instances for all types that you want to use, but you gain a lot of flexibility, plus it's idiomatic. And you can even define instances for collections from other libraries that don't happen to extend Seq.
Second solution
If you want to keep subtyping, you need to use Scala 2.13 in which the collections library went through some redesign.
There, you will find that there's a trait
trait IterableOps[+A, +CC[_], +C]
which, as you can see, retains information about the collection. Your method then becomes:
def foo[S <: immutable.SeqOps[_, Seq, S]](sa: S): S = sa.tail
foo(List(1, 2, 3)) // List(2, 3)
foo(Vector(1, 2, 3)) // Vector(2, 3)
(note that you need to avoid the A parameter in order to make it compile, because you need to return just S and not S[A])