Как я могу легко определить более сложные PartialFunctions в Scala?

PartialFunctions

В Scala a PartialFunction является, короче говоря, функцией, которая дополнительно определяет метод isDefinedAt.

Легко определить частичные функции с рядом операторов case. Тривиальным примером может быть, например:

scala> val pf: PartialFunction[Int, Unit] = {
     | case 42 => ()
     | }
pf: PartialFunction[Int,Unit] = <function1>

scala> pf.isDefinedAt(42)
res0: Boolean = true

scala> pf.isDefinedAt(0) 
res1: Boolean = false

isDefinedAt автоматически генерируется из списка case, определяющего частичную функцию.

Контекст

Рамка Lift использует частичные функции во многих местах, например. определить, должен ли запрос обрабатываться движком Lift или подаваться непосредственно из файла на диске, как есть. и иногда я хочу написать инструкцию case, которая соответствует всем входным параметрам, и только позже решить, хочу ли я вернуть значение или нет. Это означает, что исходной серии case недостаточно, чтобы определить, определена ли моя функция по заданному значению или нет

Например, в приложении "Лифт" я хочу добавить правило, что все файлы html и htm будут обслуживаться напрямую, а файлы с расширением "lift" должны быть обработаны. Было бы легко сделать что-то вроде этого:

LiftRules.liftRequest.prepend {
  case Req(path, extension, tpe) => extension match {
    case "html" | "htm" => false
    case "lift" => true
  }
}

К сожалению, в этом случае компилятор считает, что моя частичная функция определяется везде, так как первая case всегда совпадает. Это вложенный match, который может не соответствовать всем входящим запросам. И, запрос не сопоставляется, бросается MatchError.

Вопрос

Есть ли простой способ заставить компилятор рассматривать вложенные операторы match при определении частичной функции или это единственный способ сделать это, чтобы вставить все вложенные условные выражения, подобные этому?

LiftRules.liftRequest.prepend {
  case Req(path, extension, tpe) if extension == "html" || extension == "htm" => false
  case Req(path, extension, tpe) if extension == "lift" => true
}

В этом примере это в значительной степени выполнимо, но читаемость уменьшена, и я столкнулся с ситуациями, когда инкрустация всех проверок выглядит очень уродливо.

Ответ 1

В этом случае вы можете написать

LiftRules.liftRequest.prepend {
  case Req(path, "html" | "htm", tpe) => false
  case Req(path, "lift", tpe) => true
}

В более сложных случаях вам нужно определить свой собственный экстрактор, который вам придется использовать вместо вложенного оператора case.

object CheckExtension {
  def unapply(ext: String) = ext match {
    case "lift" => Some(true)
    case "html" | "htm" => Some(false)
    case _ => None
  }
}

LiftRules.liftRequest.prepend {
  case Req(path, CheckExtension(valid), tpe) => valid
}

Это будет соответствовать только если ваша предопределенная функция unapply возвращает Some и присваивает значение Some свободной переменной valid. Если unapply возвращает None, совпадение не создается.