Converters between A => Option[B], PartialFunction and extractor objects

At the moment, there are three types to represent a function that accept some of the parameters:

  1. optional functions: A => Option[B]
  2. extracter objects: { def unapply(a: A): Option[B] } and { def unapplySeq(a: A): Option[Seq[B]] }
  3. partial fucntions: PartialFunction[A, B]

Optional functions and partial functions can be converted to each other via PartialFunction.lift and Function.unlift. However, there is no simple approach to convert a partial functions to an extractor object. As a result, partial functions are not composable. You cannot create a partial function then use it as a pattern in another partial function.

This proposal provide an extract method to convert all these representation to extractor objects:

import com.thoughtworks.Extractor._

// Define a PartialFunction
val pf: PartialFunction[Int, String] = {
  case 1 => "matched by PartialFunction"
}

// Define an optional function
val f: Int => Option[String] = { i =>
  if (i == 2) {
    Some("matched by optional function")
  } else {
    None
  }
}

// Convert an optional function to a PartialFunction
val pf2: PartialFunction[Int, String] = Function.unlift(f)

util.Random.nextInt(4) match {
  case pf.extract(m) => // Convert a PartialFunction to a pattern
    println(m)
  case f.extract(m) => // Convert an optional function to a pattern
    println(m)
  case pf2.extract(m) => // Convert a PartialFunction to a pattern
    throw new AssertionError("This case should never occur because it has the same condition as `f.extract`.")
  case _ =>
    println("Not matched")
}

You can also use extract.seq to create an object with a unapplySeq method, which extracts each element of a sequence data.

import com.thoughtworks.Extractor._

val csvRow: String => Option[Seq[String]] = { line =>
  Some(line.split(','))
}

"foo,bar,baz" match {
  case csvRow.extract.seq(cell0, cell1, cell2) =>
    println(s"cell1=$cell1") // Output: cell1=bar
}

I implemented this idea as a library: Extractor.scala
The Extractor.scala library has been used in Binding.scala in order to extract patterns of XML literals: https://github.com/ThoughtWorksInc/Binding.scala/blob/10.0.x/XmlExtractor/src/main/scala/com/thoughtworks/binding/XmlExtractor.scala#L63

I wonder if you guys would like to see similar features in Scala Platform.

5 Likes

I’ll put in at least a small +1 here: the lack of composability has been kicking my ass recently, so this looks directly useful. I’ll check out the library – thanks!

Hey @yangbo,

I think this could be very useful. It is another step forward in terms of function composability and usability!

I would like to see such features in the Scala Platform. We could potentially create an extras library that hosts it and then encourage other library authors to include small and useful features like this one. That library would be a collection of a Scala utils – I would personally use it. I wonder whether you or the other guys have other features that they have implemented and are missing in the Scala standard library. Getting together all these utils can improve the Scala developer experience that we are envisioning for the Scala Platform.

1 Like

I think we could create an extras library that contains no source files, only depends on those small libraries. So that the users is able to add all these dependencies at one time, and still able to minimize their dependencies as they wish.

All my recently open-source libraries contains only one Scala source file.

For example:

  • Binding.scala is a project consists with five public libraries, one Scala source file per library.
  • enableIf.scala is a standalone macro library that contains only one Scala source file.

That could work. The only problem I see with that is that they could become more difficult to maintain. If they are hosted in different GitHub profiles or organizations, there’s a small overhead for people to hack on the project and contribute to docs/issues/code because they need to switch from repository to repository all the time.

We could sidestep these problems if we host all these utils in a repository (each one in a sbt subproject) and centralize docs/sources. Then, we could have an independent sbt subproject that depends on all the other and is the one released to the Scala Platform. The other ones will also be released independently just in case people want to have a more fine-grained control over what they pull in in their projects. What do you think @yangbo?

Sounds reasonable.

@jvican What kinds of features are suitable for the extras instead of a standalone project? Is there any other candidates except this Extractor?

I propose that we open a new thread in Discourse and start discussing potential features that the extras library could take care of. I’m sure the Community can really help there. :grin: I’ll also contact some Committee members and invite them to participate in the discussions.

On a personal note, enableIf sounds like a good candidate too!

I submitted an SIP at https://github.com/scala/docs.scala-lang/pull/1135

3 Likes

I updated the proposal to remove the extra extract method call. PartialFunction is now an extractor.

The SIP-NN is created at https://docs.scala-lang.org/sips/sips/2018-08-20-converters-among-optional-functions-partialfunctions-and-extractor-objects.html

1 Like