caeus
caeus

Reputation: 3716

How to check if function is partial in Scala?

I have a method that receives a function, but that function may be partial, in such case I don't want it to fail with MatchError.

def doSomething[X,Y](opt:Option[X])(f:X=>Y)={
  f match {
    case p:PartialFunction[X,Y]=> opt.flatMap(p.lift) //This doesn't seem to work
    case _ => opt.map(f)
  }
}

That way I can use the method like this

doSomething(x){
case t if predicate(t) =>  otherMethod(t)
}

so in case I don't have a predicate, I can use it like this doSomething(x)(otherMethod) instead of

doSoemthing(x){
case t=> otherMethod(t)
}

Note: Looking for a solution that doesn't require catching MatchError exceptions

Upvotes: 1

Views: 437

Answers (3)

SwiftMango
SwiftMango

Reputation: 15294

The syntax for partial function has been changed since 2.9 per SLS 8.5, so that even you do { case x => y}, it DOES NOT mean it is a partial function. Its type will be exact as you define it as.

In your case, you defined it as X=>Y (as in your function parameter), so it is just a X=>Y (it got compiled into a regular function, and non match cases will throw MatchError), and even you do isInstanceOf[PartialFunciton[_,_]], it won't match.

To make your scenario work, you can just simply cast the passed function as PartialFunction, like:

doSomething(Some(1))({case 2 => 0}: PartialFunction[Int,Int]) //This returns None without MatchError

while

doSomething(Some(1)){case 2 => 0} //This gives MatchError and it is not recognized as PartialFunction inside the body

This is probably not as convenient as you thought it is, but it is the only way to make it work. (or you define 2 separate functions for either case, like collect and map in standard library)

Upvotes: 1

Tim
Tim

Reputation: 27356

This isn't an answer because I don't think that what you want is possible in Scala.

The original method is fine and works as expected, though it could be a bit simpler:

def doSomething[X, Y](opt: Option[X])(f: X => Y): Option[Y] = {
  f match {
    case p: PartialFunction[X, Y] => opt.collect(p)
    case _ => opt.map(f)
  }
}

The problem is here:

doSomething(x){
  case t if predicate(t) =>  otherMethod(t)
}

Scala is creating a Function rather than a PartialFunction from that match expression so the test is failing. If you pass a real PartialFunction the method works OK.

val p: PartialFunction[Int, Int] = {
  case i: Int if i > 0 => i
}

doSomething(Some(0))(p) // Returns None

I don't think there is any way of doing what you want, mainly because doSomething has multiple argument lists which messes up type deduction for the second argument list.

My suggestion is just to use

x.map(f)

or

x.collect{
  case ...
}

as appropriate in the calling code.

Upvotes: 2

Bunyod
Bunyod

Reputation: 1371

I'm not sure what you are passing as a Partial Function, but definitely you should have to define it with specific signature like this:

val positive: PartialFunction[Int, Option[Int]] = {
  case x if x >= 0 => Some(x)
  case _ => None

The positive function is defined only for positive numbers. In case of negative numbers, the function returns None and you won't get scala.MatchError in runtime.

This specific function enables you to access to isDefinedAt method which is testing dynamically if a value is in the domain of the function.

postive(5).isDefinedAt // true

poistive.isInstanceOf[PartialFunction[Int, Option[Int]]] // true

I demonstrated here why you are always getting false when you check p.isInstanceOf

def doSomething[X,Y](opt:Option[X])(f:X=>Y)={
  f match {
    case p if p.isInstanceOf[PartialFunction[X,Y]] =>
    println("I'm a pf")
    println(s"Is it PartialFunction: ${p.isInstanceOf[PartialFunction[X,Y]]}")
    opt.map(p)
    case _ =>
    println("I'm not a pf")
    opt.map(f)
  }
}

doSomething[Int, Option[Int]](Some(5))(positive) // partial function case

doSomething[Int, String](Some(5)) { // tricky case
  case s => s.toString
}

You can play with it here:

Upvotes: -2

Related Questions