3

PartialFunction is a natural extractor, its lift method provides exact extractor functionality. So it would be very convenient to use partial functions as extractors. That would allow to combine pattern matching expressions in more complicated way than plain orElse that is available for PartialFunction

So I tried to use pimp my library approach and had failed


Here goes update: As @Archeg shown, there is another approach to conversion that works. So I'm including it to the provided code.

I'm tried also some more complex solutions and they failed


object Test {
  class UnapplyPartial[-R, +T](val fun : PartialFunction[R,T]) {
    def unapply(source : R) : Option[T] = fun.lift(source)
  }
  implicit def toUnapply[R,T](fun : PartialFunction[R,T]) : UnapplyPartial[R,T] = new UnapplyPartial(fun)

  class PartialFunOps[-R, +T](val fun : PartialFunction[R,T]) {
    def u : UnapplyPartial[R, T] = new UnapplyPartial(fun)
  }
  implicit def toPartFunOps[R,T](fun : PartialFunction[R,T]) : PartialFunOps[R,T] = new PartialFunOps(fun)


  val f : PartialFunction[String, Int] = {
    case "bingo" => 0
  }
  val u = toUnapply(f)

  def g(compare : String) : PartialFunction[String, Int] = {
    case `compare` => 0
  }

   // error while trying to use implicit conversion
  def testF(x : String) : Unit = x match {
    case f(i) => println(i)
    case _ => println("nothing")
  }

  // external explicit conversion is Ok
  def testU(x : String) : Unit = x match {
    case u(i) => println(i)
    case _ => println("nothing")
  }

 // embedded explicit conversion fails
  def testA(x : String) : Unit = x match {
    case toUnapply(f)(i) => println(i)
    case _ => println("nothing")
  }

  // implicit explicit conversion is Ok
  def testI(x : String) : Unit = x match {
    case f.u(i) => println(i)
    case _ => println("nothing")
  }

  // nested case sentences fails
  def testInplace(x : String) : Unit = x match {
    case { case "bingo" => 0 }.u(i) => println(i)
    case _ => println("nothing")
  }

  // build on the fly fails
  def testGen(x : String) : Unit = x match {
    case g("bingo").u(i) => println(i)
    case _ => println("nothing")
  }

  // implicit conversion without case is also Ok
  def testFA(x : String) : Option[Int] =
    f.unapply(x)
}

I got the following error messages:

UnapplyImplicitly.scala:16: error: value f is not a case class, nor does it have an unapply/unapplySeq member case f(i) => println(i)

UnapplyImplicitly.scala:28: error: '=>' expected but '(' found. case toUnapply(f)(i) => println(i)

This errors may be avoided with supposed form as TestI shown. But I'm curious if it is possible to avoid testInplace error:

UnapplyImplicitly.scala:46: error: illegal start of simple pattern case { case "bingo" => 0 }.u(i) => println(i) ^

UnapplyImplicitly.scala:47: error: '=>' expected but ';' found. case _ => println("nothing")

UnapplyImplicitly.scala:56: error: '=>' expected but '.' found. case g("bingo").u(i) => println(i) ^

ayvango
  • 5,867
  • 3
  • 34
  • 73

1 Answers1

4

I'm not sure what you are trying to achieve in the end, but as far as I understand extractors should always be objects, there is no way you can get it with a class. It is actually called Extractor Object in the documentation. Consider this:

class Wrapper[R, T](fun: PartialFunction[R, T]) {
  object PartialExtractor {
    def unapply(p: R): Option[T] = fun.lift(p)
  }
}

implicit def toWrapper[R,T](fun : PartialFunction[R,T]) : Wrapper[R, T] = new Wrapper(fun)


val f : PartialFunction[String, Int] = {
  case "bingo" => 0
}

def testFF(x : String) : Unit = x match {
  case f.PartialExtractor(i) => println(i)
  case _ => println("nothing")
}

Update

The best I could think of:

def testInplace(x : String) : Unit ={
  val ff = { case "bingo" => 0 } : PartialFunction[String, Int]
  x match {
   case ff.PartialExtractor(Test(i)) => println(i)
   case "sd" => println("nothing") }
}
Archeg
  • 8,364
  • 7
  • 43
  • 90
  • That really works, thanks. Is it possible to have nested case expressions? – ayvango Oct 17 '15 at 13:55
  • Sure. That's why you would want to use extractor with unapply I guess, instead of using raw `f.lift`. `case f.PartialExtractor(IntExtractor(i)) => ...` if you have `object IntExtractor { def unapply(i: Int): Option[Int] = if (i == 0) Some(5) else None }` works – Archeg Oct 17 '15 at 14:01
  • I very doubt you can inline partial function there. The case expression is very special expression, and it is not scala expression. For example all identifiers that are lowercase are considered variables, when all identifiers that are uppercase are considered types/constants. The case `f.PartialExtractor` is special, but it's still a type - it's not an expression, so I don't think you can put expressions there. And I agree that you should not be able to do that - it looks ugly and is hard to read. You can instead put a function in the body of the method, give it a name and use it. See update – Archeg Oct 17 '15 at 14:13
  • I tried, see update. I've defined function g as String => PartialFunction[String, Int], and it also got rejected – ayvango Oct 17 '15 at 14:15
  • As I said, case expression is very special expression, and you just can't put there anything. It is parsed by special rules. I believe that you cannot put any method invoke there, but if you want to know more about how they are parsed, you probably should look at scala compiler sources. Notice that `ff.PartialExtractor(..)` is not a method invoke. If you write `ff.PartialExtractor(..)` in normal scala expression, `.apply()` is called. But in case expression `.unapply()` is called. Case expressions are very different than normal code – Archeg Oct 17 '15 at 14:20
  • You should think of case expression as a pattern match. This expression is not computed over your value, but rather matched over it. And you cannot match a method call, but you can match the value that the method call returns – Archeg Oct 17 '15 at 14:22
  • I suppose that case part of match expression is not expression itself, so it could not be computed. It like writing `val func(x)(y) = 4` from the compiler point of view. That is a pity, since it is easy to distinguish if object has `unapply` method and use it if exist and use `apply` in the other case to try to build some object that could have `unapply` – ayvango Oct 17 '15 at 14:31
  • Exactly. BTW, you can match values in scala like `val (a, b) = 2 -> 3`. And this is exactly the same semantics you use when pattern matching. – Archeg Oct 17 '15 at 14:34
  • @Archeg *"as far as I understand extractors should always be objects, there is no way you can get it with a class"* Extractors can be classes. For example see `class GreaterThanOrEmpty(dataBound: Int)` in https://stackoverflow.com/questions/62039721/scala-pattern-matching-on-none-and-some-in-the-same-case or `class All[SubT: ClassTag]` in https://stackoverflow.com/questions/64554808/how-to-pattern-match-on-the-types-of-list-elements – Dmytro Mitin Mar 31 '23 at 01:34
  • @DmytroMitin Well, it has been some time when I used scala, but as far as I can see, both examples create objects from these clases. For `GreaterThanOrEmpty` we have this `val GreaterThan50OrEmpty = new GreaterThanOrEmpty(50)` which is an object. Not in a sense of object keyword, but in the sense of object value. I might be wrong of course as I haven't been coding scala for years – Archeg Apr 11 '23 at 09:24
  • @Archeg In such sense classes do not exist :) because always when they are instantiated this ends with "objects". But what is the meaning of *"extractors should always be objects, there is no way you can get it with a class"* then? – Dmytro Mitin Apr 11 '23 at 09:30
  • @DmytroMitin Then you can say objects do not exist either, because they end up in bytes anyway :) Just read the documentation. https://docs.scala-lang.org/tour/extractor-objects.html `An extractor object is an **object** with an unapply method` – Archeg Apr 11 '23 at 09:53
  • @Archeg Well, this is just "Tour of Scala". In Scala spec there is better formulation *"extractor pattern `x(p1​,…,pn​)`"*, *"stable identifier `x`"* https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#extractor-patterns Although again unlucky formulation *"`x` denotes an object..."*. Unlucky because in the spec, *"object"* refers only to `object`. So important is whether `x` is stable, not whether it refers to a class or object. – Dmytro Mitin Apr 11 '23 at 10:50
  • @DmytroMitin As I read it, I still think that mentioning `object` term there is deliberate. Honestly, I think we are just arguing here about terms and not really about anything else, I don't think there is any point in this – Archeg Apr 11 '23 at 12:48