Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is `PartialFunction extends Function` a violation of LSP?

The Liskov Substitution Principle state that

if S is a subtype of T, then objects of type T may be replaced with objects of type S 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 pfwithout altering my program. So in my opinion it is a violation of LSP.

What do you think ?

like image 877
gervais.b Avatar asked Nov 03 '16 13:11

gervais.b


2 Answers

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.

like image 64
Alexey Romanov Avatar answered Sep 24 '22 20:09

Alexey Romanov


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.

like image 29
A Spoty Spot Avatar answered Sep 21 '22 20:09

A Spoty Spot