Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I easily define more complex PartialFunctions in Scala?

PartialFunctions

In Scala, a PartialFunction is, in short, a function that additionally defines an isDefinedAt method.

It is easy to define partial functions with a series of case statement. A trivial example would be, e.g.:

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 is automatically generated from the list of cases defining the partial function.

Context

The Lift framework makes use of partial functions in many places, e.g. to define whether a request should be processed by Lift's engine or served directly from a file on disk, as is. and sometimes, I find myself wanting to write a case statement that matches all input parameters and only later decide if I want to return a value or not. This means that the initial series of cases is not enough any more to determine if my function is defined at a given value or not

For instance, in Lift, I want to add a rule that all html and htm files are served directly, and that files with the “lift” extension should be processed. It would look easy to do something like this:

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

Unfortunately, in this case, the compiler thinks that my partial function is defined everywhere, as the first case always matches. It's the nested match that may not match all incoming requests. And, is a request is not matched, a MatchError is thrown.

Question

Is there a simple way to make the compiler consider nested match statements when defining a partial function, or is the only way to do it to inline all nested conditionals like this?

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

In this example, it's largely doable, but the readability is decreased, and I've faced cases where inlining all checks looks very ugly.

like image 454
Jean-Philippe Pellet Avatar asked Jul 07 '11 10:07

Jean-Philippe Pellet


People also ask

How do you define a partial function in Scala?

Therefore, we can define a partial function for each requirement: val negativeOrZeroToPositive: PartialFunction[Int, Int] = { case x if x <= 0 => Math.abs(x) } val positiveToNegative: PartialFunction[Int, Int] = { case x if x > 0 => -1 * x }

When to use partial function Scala?

When a function is not able to produce a return for every single variable input data given to it then that function is termed as Partial function. It can determine an output for a subset of some practicable inputs only. It can only be applied partially to the stated inputs.

Why use partial function?

A partial function allows us to call a second function with fixed values in certain arguments. For instance, we may have a function that computes an exponentiation. Then, we may need to create a new function that assigns a fixed value to either the base or the exponent.


1 Answers

In this case, you may want to write

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

For more complicated cases, you’ll need to define your own extractor which you’ll have to use instead of a nested case statement.

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
}

This will only match if your predefined unapply function returns Some and assign the value of Some to the free variable valid. If unapply returns None, no match is being generated.

like image 92
Debilski Avatar answered Oct 24 '22 22:10

Debilski