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

Частичные функции

В Scala 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 автоматически генерируется из списка cases, определяющих частичную функцию.

Контекст

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

Например, в Lift я хочу добавить правило, что все файлы 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
}

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


person Jean-Philippe Pellet    schedule 07.07.2011    source источник


Ответы (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, совпадение не создается.

person Debilski    schedule 07.07.2011
comment
Спасибо! Тогда настраиваемые экстракторы — это то, что вам нужно. Хорошее решение — хотя немного сложнее следить за тем, что происходит… - person Jean-Philippe Pellet; 07.07.2011
comment
Вместо этого вы можете написать case Req(path, ext @ CheckExtension(valid), tpe), чтобы явно показать, что вам дает Req. - person Debilski; 08.07.2011
comment
Вау! Я понятия не имел, что вы можете использовать альтернативы внутри экстрактора шаблонов. Спасибо - person Bill; 09.07.2011