You actually do not need the A <: Foo
in neither of them and on its own it would be a another different function.
So, assuming this:
trait Foo {
def show(): Unit
}
trait Bar {
def value: Int
}
final case class Baz(value: Int) extends Foo with Bar {
override final def show(): Unit = {
println(s"Baz { value : ${value} }")
}
}
Lets see what we can do:
def f(a: Foo): Foo = {
a.show()
a
}
Is basic subtyping polymorphism.
You can call f
with any value that is a subtype of Foo, as such you can call anything that Foo defines; because due Liskov it has to have all that.
However, you lose the information of the precise subtype it was at the return, you only know you have a Foo.
def g[A <: Foo](a: A): A = {
a.show()
a
}
Is very similar to the previous one.
However, in this case we preserve the information of the precise subtype.
So if I call this method with a Baz I will have a return type of Baz instead of Foo.
def h[A](a: A)(implicit ev: A <:< Foo): A = {
a.show() // We can call show, because we know that a is a subtype of Foo.
a
}
Is also very similar to the previous one.
The difference is when we do the subtype check.
With just one type there is usually no difference, but when there is more than one there are cases when <:
wont work as we want whereas <:<
will do.
Check this for a detailed explanation.
def i[A](a: A)(implicit ev: A => Foo): A = {
x.show() // We can call show because we know how to convert an A into a Foo.
x
}
Again, very similar to the previous one.
However more flexible, since we only demand a conversion, not a subtype relationship; note that A <:< Foo
implies A => B
.
Also, the previous one will only be generated if the compiler can prove the subtyping relationship. Whereas, for this one anyone can provide the conversion (as long as it is in the correct implicit scope).
def j(a: Foo with Bar): Foo = {
a.show()
println(a.value)
a
}
Is very similar to the first one.
Only that here we also demand a
to also be a subtype of Bar not only of Foo.
(and the order matters, but that will be fixed in Scala 3).
All of them have valid use cases.
But the most common ones are f
, g
& h
.