8

Is there any way to create a PartialFunction except through the case statement?

I'm curious, because I'd like to express the following (scala pseudo ahead!)...

val bi = BigInt(_)
if (bi.isValidInt) bi.intValue

... as a partial function, and doing

val toInt : PartialFunction[String, Int] = {
    case s if BigInt(s).isValidInt => BigInt(s).intValue
}

seems redundant since I create a BigInt twice.

0__
  • 66,707
  • 21
  • 171
  • 266
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • I think the important question besides typing is: What do I have to return/throw in the undefined case such that it will be recognised as such by `orElse` et al? – Raphael Apr 15 '11 at 19:57

5 Answers5

5

Not sure I understand the question. But here's my attempt: Why not create an extractor?

object ValidBigInt {
  def unapply(s: String): Option[Int] = {
    val bi = BigInt(s)
    if (bi.isValidInt) Some(bi.intValue) else None
  }
}

val toInt: PartialFunction[String, Int] = {
  case ValidBigInt(i) => i
}

The other option is (and that may answer the question as to whether one can create PartialFunction other than with a case literal):

val toInt = new PartialFunction[String, Int] {
  def isDefinedAt(s: String) = BigInt(s).isValidInt
  def apply(s: String) = BigInt(s).intValue
}

However since the idea of a partial function is that it's only partially defined, in the end you will still do redundant things -- you need to create a big int to test whether it's valid, and then in the function application you create the big int again...

I saw a project at Github that tried to come around this by somewhat caching the results from isDefinedAt. If you go down to the benchmarks, you'll see that it turned out to be slower than the default Scala implementation :)

So if you want to get around the double nature of isDefinedAt versus apply, you should just go straight for a (full) function that provides an Option[Int] as result.

0__
  • 66,707
  • 21
  • 171
  • 266
  • The reason that I want a partial function is that I'm appending it after a `^?` parser combinator. I like your idea with `unapply`. Never used that. Thanks! – aioobe Apr 14 '11 at 19:49
  • Actually, under the right condition the caching variants of a partial function _are_ faster; he needs to do some optimizations manually, but I'm shocked that JITs don't do it. – Blaisorblade Apr 20 '12 at 21:05
4

I think you're looking for lift/unlift. lift takes a partial function and turns it into a function that returns an Option. Unlift takes a function with one argument that returns an option, and returns a partial function.

import scala.util.control.Exception._

scala> def fn(s: String) = catching(classOf[NumberFormatException]) opt {BigInt(s)}
fn: (s: String)Option[scala.math.BigInt]

scala> val fnPf = Function.unlift(fn)
fnPf: PartialFunction[String,scala.math.BigInt] = <function1>

scala> val fn = fnPf.lift
fn: String => Option[scala.math.BigInt] = <function1>

Closely related, you also want to look at this answer for information about cond and condOpt:

scala> import PartialFunction._
import PartialFunction._

scala> cond("abc") { case "def" => true }
res0: Boolean = false

scala> condOpt("abc") { case x if x.length == 3 => x + x }
res1: Option[java.lang.String] = Some(abcabc)
Community
  • 1
  • 1
James Moore
  • 8,636
  • 5
  • 71
  • 90
3

You can write out a PartialFunction "longhand" if you'd like:

object pf extends PartialFunction[Int,String] {
  def isDefinedAt(in: Int) = in % 2 == 0

  def apply(in: Int) = {
    if (in % 2 == 0) 
      "even" 
    else 
      throw new MatchError(in + " is odd")
}
Alex Cruise
  • 7,939
  • 1
  • 27
  • 40
  • So `MatchError` signals undefinedness? – Raphael Apr 15 '11 at 19:56
  • Well, you can do whatever you want here, but the PartialFunction literal code generation basically just embeds a pattern matching expression into the apply method, and that's what pattern matching expressions do as the last case (at least conceptually--the code generated by the pattern matcher is very interesting to read!) – Alex Cruise Apr 15 '11 at 21:12
1

Okay, I got this

import java.lang.NumberFormatException
import scala.util.control.Exception._

val toInt: PartialFunction[String, Int] = {
  catching(classOf[NumberFormatException]) opt BigInt(_) match {
    case Some(bi) if bi.isValidInt => bi.intValue
  }
}
elbowich
  • 1,941
  • 1
  • 13
  • 12
0

How about this?

val toInt: PartialFunction[String, Int] = (s: String) => BigInt(s) match {
  case bi if bi.isValidInt => bi.intValue
}
elbowich
  • 1,941
  • 1
  • 13
  • 12