1

I'm trying to use a partial function for some validations, lets take an example of a string:

def isLengthValid: PartialFunction[String, Option[String]] ={
  case s:String if s.length > 5 => Some("Invalid")
}

def isStringValid: PartialFunction[String, Option[String]] ={
  case s: String if s == "valid" => Some("Valid")
}

isLengthValid("valid") orElse isStringValid("valid")

expected output => Some("Valid")

But I'm getting a match Error:

scala.MatchError: valid (of class java.lang.String)

Could anyone help that what is going wrong here, because as per my understanding .isDefinedAt is called internally and is should not give matchError.

P.S ignore the inputs, this is just an example.

geek94
  • 443
  • 2
  • 11

3 Answers3

5

Your understanding is wrong. The ScalaDocs page clearly states, "It is the responsibility of the caller to call isDefinedAt before calling apply ...".

Your code isn't calling isDefinedAt, which is causing the exception, so you have to explicitly call it or you can employ other methods that hide the isDefinedAt internally.

Seq("valid") collect (isLengthValid orElse isStringValid)
//res0: Seq[Option[String]] = List(Some(Valid))

Seq("vlad") collect (isLengthValid orElse isStringValid)
//res1: Seq[Option[String]] = List()
jwvh
  • 50,871
  • 7
  • 38
  • 64
4

This works as intended if you write the last line as

(isLengthValid orElse isStringValid)("valid")

I suspect the problem is that your version desugars to (isLengthValid.apply("valid")).orElse(isStringValid.apply("valid"))

This means the apply is calculated before the orElse takes place, which means the partial function is treated as a total function and a match error is thrown as Valy Dia's answer explains. The orElse is actually being called on the result output, not the partial function.

Astrid
  • 1,808
  • 12
  • 24
  • Thanks this works, and this was what was i was looking for – geek94 Jun 04 '19 at 08:37
  • 1
    @geek94; Really? This is what you were looking for? But this still throws a `matchError` when you try to validate a string like `"vld"`. That's dangerous code. – jwvh Jun 04 '19 at 08:41
2

The error message comes from the first expression - isLengthValid.

It is define only for string with a length stricly greater than 5. Hence when it is applied to a the string "valid" of length 5, it throws a MatchError:

scala>"valid".length
res5: Int = 5

isLengthValid("valid")
scala.MatchError: valid (of class java.lang.String)

If the method isLengthValid defined this way instead (Note the greater than equal sign), it wouldn't throw the MatchError :

def isLengthValid: PartialFunction[String, Option[String]] ={
  case s:String if s.length >= 5 => Some("Invalid")
}

scala>isLengthValid("valid")
res8: Option[String] = Some("Invalid")

And the original expression would return an Option:

scala>isLengthValid("valid") orElse isStringValid("valid")
res9: Option[String] = Some("Invalid")

What you could do here as well as explained in this question, is to use this definition instead:

val isLengthValid = new PartialFunction[String, Option[String]] {
  def isDefinedAt(x: String) = x.length > 5
  def apply(x: String) = Some("Invalid")
}

scala>isLengthValid("valid")
res13: Some[String] = Some("Invalid")
Valy Dia
  • 2,781
  • 2
  • 12
  • 32