Reputation: 2347
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 ?
Upvotes: 6
Views: 159
Reputation: 170713
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.
Upvotes: 6
Reputation: 746
According to wikipedia there is an additional clause required for the LSP to apply.
No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the supertype.
So (assuming wikipedia is correct), no PartialFunction doesn't violate the LSP since it doesn't apply to this case.
EDIT: There is additional info in the scaladoc: (Scaladoc Function1)
Note that Function1 does not define a total function, as might be suggested by the existence of PartialFunction. The only distinction between Function1 and PartialFunction is that the latter can specify inputs which it will not handle.
So another way of thinking about it would be that pf is not a subtype of fn.
fn: String => Int
pf: Subset of String => Int
Further edit in response to Comment: No I'm arguing that the LSP doesn't apply at all. For the LSP to apply S must not throw new exceptions. S here does throw new exceptions so the LSP cannot be violated since it doesn't apply.
Upvotes: 1