1

I am trying to come up with a combinator that would allow me do something like this:

 def pfAdapter(pf: PartialFunction[String, String]): PartialFunction[(String,String), String]) = { 
   case (a,b) if(pf.isDefinedAt(a)) => pf(a)
 }

Basically, I have a Map[String, String], and want to use it as a PartialFunction that takes two parameters - ignore the second element, and use the first one as a key.

The approach above works, but I don't like the fact that pf essentially gets evaluated twice (there may be no way around that), and that it's not "elegant" ... I was wondering if there is some kind of a combinator I don't know about that would let me do something like { _._1 } andThen pf. This last attempt doesn't work, obviously, because the result of it is always defined, and will simply fail on non-existent keys, I am just using it as an illustration of how the solution I am looking for would ideally look.

Ideas, anyone?

Dima
  • 39,570
  • 6
  • 44
  • 70
  • So you're OK with an exception if the key doesn't exist? Or am I missing something? – The Archetypal Paul May 18 '16 at 11:44
  • No, I want a PartialFunction, that is defined on tuples, having their first element present as a key in map. No exceptions :) – Dima May 18 '16 at 12:18
  • Then how is it partial? What is the desired outcome when the first element is not present? – The Archetypal Paul May 18 '16 at 12:19
  • Like any partial function: if you call it with a value outside of its domain, you get an exception. So, in that sense, yes, I am ok with it :). The idea, however us for it to never be called on such values, because it would have `lift`, `isDefinedAt`, `orElse`, etc. defined properly. – Dima May 18 '16 at 12:21
  • Ah ha! So, isn't `if(pf.isDefinedAt(a)) => pf(a)` exactly the same as `=> pf(a)`? Both will work if the pf is defined at a, and give an exception if not – The Archetypal Paul May 18 '16 at 12:24
  • The behavior is the same when you do `outerFunction(badKey -> unusedX)`, yes, but it is different for `outerFunction.isDefinedAt(badKey -> unusedX)` or, for example, for `outerFunction orElse getDefault` etc. – Dima May 18 '16 at 13:10

1 Answers1

2

The function itself (pf.apply) is not really evaluated twice, but its isDefinedAt is evaluated twice for successful matches with your definition. And that means evaluating twice unapply-s and guards in the initial PartialFunction pf.

By the way there is a combinator in Scalaz that does a similar thing: pf.first.andThen(_._1), but it is basically equivalent to your definition.

You can write a small test, to see if pf.isDefinedAt is evaluated twice and run it with several possible implementations of pfAdapter:

object Unapply {
  def unapply(s: String): Boolean = {
    println(s"unapplying on $s")
    s == "1"
  }
}

val m = Map("1" -> 1, "2" -> 2)
def pf: PartialFunction[String, String] = {
  case Unapply() => "11"
}

def pfAdapter1[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] =
  Function.unlift((t: (A, T)) => pf.lift(t._1))

def pfAdapter2[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] =
  new PartialFunction[(A, T), B] {
    def isDefinedAt(arg: (A, T)) = pf.isDefinedAt(arg._1)
    def apply(arg: (A, T)) = pf(arg._1)
  }

def pfAdapter3[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] = {
  case (a,b) if pf.isDefinedAt(a) => pf(a)
}

def pfAdapter4[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] = {
  import scalaz.Scalaz._
  pf.first.andThen(_._1)
}

println(m collect pfAdapter1(pf))
println(m collect pfAdapter2(pf))
println(m collect pfAdapter3(pf))
println(m collect pfAdapter4(pf))

And the result of executing this code is as follows:

unapplying on 1
unapplying on 2
List(11)
unapplying on 1
unapplying on 1
unapplying on 2
List(11)
unapplying on 1
unapplying on 1
unapplying on 2
List(11)
unapplying on 1
unapplying on 1
unapplying on 2
List(11)

So the first implementation of pfAdapter: Function.unlift((t: (A, T)) => pf.lift(t._1)) actually does avoid evaluating isDefinedAt twice.

This works, because Map.collect is implemented with PartialFunction.applyOrElse, and the documentation for applyOrElse states, that:

For all partial function literals the compiler generates an applyOrElse implementation which avoids double evaluation of pattern matchers and guards. This makes applyOrElse the basis for the efficient implementation for many operations and scenarios, such as:

...

  • lift and unlift do not evaluate source functions twice on each invocation
Kolmar
  • 14,086
  • 1
  • 22
  • 25