Shreck Ye
Shreck Ye

Reputation: 1859

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)
}

Upvotes: 1

Views: 126

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170745

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
}

Upvotes: 4

Mario Galic
Mario Galic

Reputation: 48420

Technically, you could override isDefinedAt by directly defining PartialFunction like so

def decodeList: PartialFunction[Json, List[A]] = new PartialFunction[Json, List[A]] {
  override def apply(json: Json): List[A] = json match {
    case Json.Arr(items) =>
      val options = items map decode
      options map (_.get)
  }

  override def isDefinedAt(json: Json): Boolean = json match {
    case Json.Arr(items) =>
      val options = items map decode
      options forall (_.isDefined)
  }
}

however this differs from the kind of isDefinedAt compiler would provide by default.

Upvotes: 2

Related Questions