The Liskov Substitution Principle state that
if
S
is a subtype ofT
, then objects of typeT
may be replaced with objects of typeS
without altering any of the desirable properties of that program.
However, in Scala, there is the PartialFunction
that is not applicable/defined in all cases.
trait Function1 {
def apply(x: A): R
}
trait PartialFunction extends Function1 {
def apply(x: A): R
def isDefinedAt(x: A): Boolean
}
If you apply a PartialFunction
to an undefined value, you will receive an exception.
A convenient way to create a PartialFunction
in scala is to use pattern matching. Doing so, you receive a MatchError
for undefined values.
val fn:Function1[String, Int] = s => s.length
val pf:PartialFunction[String, Int] = {
case "one" => 3
}
def program(f:Function1[String, Int], s:String):(Boolean, Int) = (
f.isInstanceOf[Function1[String, Int]], f(s)
)
program(fn, "one") == program(pf, "one")
program(fn, "two") == program(pf, "two")
fn: String => Int = <function1>
pf: PartialFunction[String,Int] = <function1>
program: program[](val f: String => Int,val s: String) => (Boolean, Int)
res0: Boolean = true
scala.MatchError: two (of class java.lang.String)
at scala.PartialFunction$$anon$1.apply(delme.sc:249)
at scala.PartialFunction$$anon$1.apply(delme.sc:247)
at ...
Both fn
and pf
are subtypes of Function1
, but I cannot substitute fn
by pf
without altering my program
. So in my opinion it is a violation of LSP.
What do you think ?
fn
and pf
are not subtypes of Function1
, since they aren't types at all. They are values, and LSP doesn't say you can take any object of type T
and replace it with any object of type S
: Int
is certainly a subtype of Int
, but replacing 1
with 2
can make a correct program incorrect.
PartialFunction[String, Int]
is a subtype of Function1[String, Int]
, but the contract of Function1
doesn't forbid apply
from throwing exceptions and in fact explicitly allows it:
Apply the body of this function to the argument. It may throw an exception.
So if your program can handle objects of type Function1[A, B]
, it has to deal with apply
throwing exceptions in some way already and PartialFunction
throwing a MatchError
is just a specific case.