scalapattern-matchingpartialfunction

In a pattern matching partial function, how to make isDefined return false for invalid inputs that can't be included in the case pattern?


In a partial function implemented with pattern matching, how to make isDefined return false for invalid inputs that can't be included in the case pattern?

For example, I have the following decodeList partial function:

case class Arr(items: List[Json]) extends Json
def decode(data: Json): Option[A]
def decodeList: PartialFunction[Json, List[A]] = {
  case Json.Arr(items) =>
    val options = items map decode
    if (options forall (_.isDefined)) options map (_.get)
    else throw new Error // the partial function should be undefined here
}

I want to change the code in a way so that decodeList.isDefinedAt evaluates to false for invalid inputs. For example, for an a that decode(a) evaluates to None ,decodeList.isDefinedAt(Json.Arr(List(a))) should evaluate to false.

Or from another perspective, if I try to include the condition in the case pattern as in the following code, where shall I put the val options = items map decode definition so it can be reusable by both the case pattern and the block?

def decodeList: PartialFunction[Json, List[A]] = {
  case Json.Arr(items) if (options forall (_.isDefined)) => 
   options map (_.get)
}

Solution

  • You can do this by defining a custom extractor object, e.g.

    object Options {
      def unapply(items: List[Json]) = Some(items map decode)
    }
    
    def decodeList: PartialFunction[Json, List[A]] = {
      case Json.Arr(Options(options)) if (options forall (_.isDefined)) => 
       options map (_.get)
    }
    

    which isn't particularly convenient, but I don't know a better way.

    Of course, I'd suggest actually defining def decodeList(list: Json): Option[List[A]] which fits better with decode and doesn't need such workarounds; then Function.unlift(decodeList) if you need a PartialFunction.

    def decodeList(list: Json) = list match {
      case Json.Arr(items) => 
        val options = items map decode
        if (options forall (_.isDefined)) Some(options map (_.get)) else None
      case _ => None
    }