Scala Pattern Matching pretty printed
Asked Answered
G

2

6

Is that possible to somehow marshall PartialFunction (let's assume it will always contains only one case) into something human-readable?

Let's say we have collection of type Any (messages: List[Any]) and number of PartialFuntion[Any, T] defined using pattern matching block.

case object R1
case object R2
case object R3

val pm1: PartialFunction[Any, Any] = {
  case "foo" => R1
}

val pm2: PartialFunction[Any, Any] = {
  case x: Int if x > 10 => R2
}

val pm3: PartialFunction[Any, Any] = {
  case x: Boolean => R3
}

val messages: List[Any] = List("foo", 20)
val functions = List(pm1, pm2)

then we can find all the messages matched by provided PFs and related applications

val found: List[Option[Any]] = functions map { f =>
  messages.find(f.isDefined).map(f)
}

but what if I need resulting map of 'what I expect' to 'what I've got' in the human-readable form (for logging). Say,

(case "foo")         -> Some(R1)
(case Int if _ > 10) -> Some(R2)
(case Boolean)       -> None

Is that possible? Some macro/meta works?

Gastrotrich answered 24/6, 2015 at 21:25 Comment(1)
Neat idea. I have to say more to comment at all... Obviously, the alternative is other side-effects.Impugn
G
1

Thanks for your answers. Using Macro is interesting one choice. But as an option the solution might be to use kind of named partial functions. The idea is to name the function so in the output you can see the name of function instead source code.

object PartialFunctions {

  type FN[Result] = PartialFunction[Any, Result]

  case class NamedPartialFunction[A,B](name: String)(pf: PartialFunction[A, B]) extends PartialFunction[A,B] {
    override def isDefinedAt(x: A): Boolean = pf.isDefinedAt(x)
    override def apply(x: A): B = pf.apply(x)
    override def toString(): String = s"matching($name)"
  }

  implicit class Named(val name: String) extends AnyVal {
    def %[A,B](pf: PartialFunction[A,B]) = new NamedPartialFunction[A, B](name)(pf)
  }

}

So then you can use it as follows

import PartialFunctions._

val pm1: PartialFunction[Any, Any] = "\"foo\"" % {
  case "foo" => R1
}

val pm2: PartialFunction[Any, Any] = "_: Int > 10" % {
  case x: Int if x > 10 => R2
}

val pm3: PartialFunction[Any, Any] = "_: Boolean" % {
  case x: Boolean => R3
}

val messages: List[Any] = List("foo", 20)
val functions = List(pm1, pm2)

val found: List[Option[(String, Any)]] = functions map { case f: NamedPartialFunction =>
  messages.find(f.isDefined).map(m => (f.name, f(m))
}
Gastrotrich answered 21/8, 2015 at 9:43 Comment(0)
N
2

There's nothing at runtime which will print compiled code nicely.

You could write a macro which will print the source code of the tree and use that? Most macro tutorials start with a macro for printing source code -- see e.g. http://www.warski.org/blog/2012/12/starting-with-scala-macros-a-short-tutorial/

Perhaps:

// Given a partial function "pf", return the source code for pf
// as a string as well as the compiled, runnable function itself
def functionAndSource(pf: PartialFunction[Any, Any]): (String, PartialFunction[Any, Any]) = macro functionAndSourceImpl

def functionAndSourceImpl ...

val pm1: (String, PartialFunction[Any, Any]) = functionAndSource {
  case "foo" => R1
}

This isn't ever going to be that easy or nice in Scala. Scala isn't Lisp or Ruby: it's a compiled language and it is not optimised for reflection on the code itself.

Novella answered 17/8, 2015 at 14:15 Comment(0)
G
1

Thanks for your answers. Using Macro is interesting one choice. But as an option the solution might be to use kind of named partial functions. The idea is to name the function so in the output you can see the name of function instead source code.

object PartialFunctions {

  type FN[Result] = PartialFunction[Any, Result]

  case class NamedPartialFunction[A,B](name: String)(pf: PartialFunction[A, B]) extends PartialFunction[A,B] {
    override def isDefinedAt(x: A): Boolean = pf.isDefinedAt(x)
    override def apply(x: A): B = pf.apply(x)
    override def toString(): String = s"matching($name)"
  }

  implicit class Named(val name: String) extends AnyVal {
    def %[A,B](pf: PartialFunction[A,B]) = new NamedPartialFunction[A, B](name)(pf)
  }

}

So then you can use it as follows

import PartialFunctions._

val pm1: PartialFunction[Any, Any] = "\"foo\"" % {
  case "foo" => R1
}

val pm2: PartialFunction[Any, Any] = "_: Int > 10" % {
  case x: Int if x > 10 => R2
}

val pm3: PartialFunction[Any, Any] = "_: Boolean" % {
  case x: Boolean => R3
}

val messages: List[Any] = List("foo", 20)
val functions = List(pm1, pm2)

val found: List[Option[(String, Any)]] = functions map { case f: NamedPartialFunction =>
  messages.find(f.isDefined).map(m => (f.name, f(m))
}
Gastrotrich answered 21/8, 2015 at 9:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.