Thou whoreson zed! thou unnecessary letter!

- King Lear, 2.2.61

Partial functions are great in theory. By "great in theory", I mean that the basic idea is pretty simple but with very little additional research you can make weighty pronouncements involving the words "in" and "theory", as in, "in category theory, something something surjective algebra manifold."

The simplest example of a partial function is something like the logarithm, which isn't defined for arguments less than or equal to zero. Of course, it's also not defined for arguments that are strings or colors or egg-laying mammals, a restriction we usually discuss in terms of types. And one can discuss a function's domain in terms of type, but we usually choose not to.

In Scala, when you write a normal function, like d: Double => Math.log(d), you get a Function, which is just a class that happens to have an apply method

    object loggy extends Function1[Double,Double] {
       def apply(d: Double): Double = Math.log(d)
    }

and a PartialFunction is just like this, except you can ask where it is defined:

    object ploggy extends PartialFunction[Double,Double] {
       def apply(d: Double): Double = Math.log(d)
       def isDefinedAt(d: Double): Boolean  = d > 0.0
    }

If you just call ploggy(0.0), you'll get the usual floating point exception, but Scala gives you other, spiffier ways to invoke it, such as

    ploggy.applyOrElse(0.0, 42*_)

where we've taken the small liberty of assuming that if you passed a negative argument to our log function, you probably meant to multiply by 42 instead, and

     (ploggy orElse anotherPartialFunction)(0.0)

where our backup function is also partial.

Those formulations are not commonly used. More popular is the collect combinator

     Seq(-2.0,1.0).collect(ploggy)

which combines a filter on .isDefinedAt and map over what's left, yielding Seq(0.0) in this case. It is even more common to see collect used with a match-like syntax

    Some(0).collect {
      case 1 => "Hello"
      case 2 => "Goodbye"
    }

here applied to an Option rather than a sequence and in this case expected to return None.

If we ask politely,

    scala -Xprint:typer -e 'Some(1).collect { case 1 => "Hello"; case 2 => "Goodbye"}'

Scala will reveal the trained hippopotamus behind the curtain:

     ...
        scala.Some.apply[Int](1).collect[String](({
          @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[Int,String] with Serializable {
            def <init>(): <$anon: Int => String> = {
              $anonfun.super.<init>();
              ()
            };
            final override def applyOrElse[A1 <: Int, B1 >: String](x1: A1, default: A1 => B1): B1 = ((x1.asInstanceOf[Int]: Int): Int @unchecked) match {
              case 1 => "Hello"
              case 2 => "Goodbye"
              case (defaultCase$ @ _) => default.apply(x1)
            };
            final def isDefinedAt(x1: Int): Boolean = ((x1.asInstanceOf[Int]: Int): Int @unchecked) match {
              case 1 => true
              case 2 => true
              case (defaultCase$ @ _) => false
            }
          };
          new $anonfun()
        }: PartialFunction[Int,String]))
      };
   ...

This is no ordinary hippopotamus. The scala typer has divined the presence of a match block where a PartialFunction is expected

          case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) => ...

and replicated our innocuous looking switch clause into two different methods of a brand new class definition. It feels imbalanced, a conflation of concerns, that the central typing logic of the compiler is in the business of extending one very specific class in scala-library.jar.

Oh Snozzcumbers

    ‘It’s disgusterous!, the BFG gurgled. ‘It’s sickable! It’s
    rotsome! It’s maggotwise! Try it yourself, this foulsome
    partial function!'

    ‘No, thank you,‘ Sophie said, backing away.  ‘It’s all you’re
    going to be guzzling around here from now on so you might as well
    get used to it,’ said the BFG. ‘Go on, you snipsy little winkle,
    have a go!’

    Sophie took a small nibble. ‘Ugggggggh!’ she spluttered. ‘Oh no!
    Oh gosh! Oh help!’ She spat it out quickly. ‘It tastes of
    frogskins!’ she gasped. ‘And rotten fish!’

    ‘Worse than that!’ cried the BFG, roaring with laughter. ‘To me it
    is tasting of clockcoaches and slime-wanglers!’

And yet Dahl was writing in the context of an alternative to cannibalism, while partial functions are an alternative to maybe... well, an alternative to Maybe, or Option as we Scalactites call it. Some(1).flatMap{case 1 => Some("Hello"); case 2 => Some("Goodbye"); case _ => None} emerges from the typer without significant change:

        scala.Some.apply[Int](1).flatMap[String](((x0$1: Int) => x0$1 match {
          case 1 => scala.Some.apply[String]("Hello")
          case 2 => scala.Some.apply[String]("Goodbye")
          case _ => scala.None
        }))

I'm not being entirely fair, because the lambda is going to get turned into a Function1 during the uncurry phase,

    scala -Xprint:uncurry  -e 'Some(1).flatMap {case 1 => Some("Hello"); case 2 => Some("Goodbye"); case _ => None}'
        Some[Int](1).flatMap[String]({
          @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction1[Int,Option[String]] with Serializable {
            def <init>(): <$anon: Int => Option[String]> = {
              $anonfun.super.<init>();
              ()
            };
            final def apply(x0$1: Int): Option[String] = {
              case <synthetic> val x1: Int = x0$1;
              x1 match {
                case 1 => new Some[String]("Hello")
                case 2 => new Some[String]("Goodbye")
                case _ => scala.None
              }
            }
          };
          (new <$anon: Int => Option[String]>(): Int => Option[String])
        })

While we're spared gratuitous doubling of the core logic, we still get a bespoke .class file and constructor.

Lambda functions are the new normal

But not for long! With Scala 2.12 and Java 1.8, lambda functions will be implemented with invoke-dynamic. Under 2.11, we can get a taste of this advanced feature with -Ydelambdafy:method,

    scala -Ydelambdafy:method -Xprint:uncurry  -e 'Some(1).flatMap {case 1 => Some("Hello"); case 2 => Some("Goodbye"); case _ => None}'
        Some[Int](1).flatMap[String]({
          final <artifact> def $anonfun(x0$1: Int): Option[String] = {
            case <synthetic> val x1: Int = x0$1;
            x1 match {
              case 1 => new Some[String]("Hello")
              case 2 => new Some[String]("Goodbye")
              case _ => scala.None
            }
          };
          ((x0$1: Int) => $anonfun(x0$1))
        })

which, again, doesn't look that much different from the original. Now that lambda functions have gone mainstream, now that Brian Goetz has decreed that they should be pretty and efficient, Scala doesn't have to try so hard to fake them. Specifically, the obvious way to fake them as been hardwired into the JVM - Single Method Interfaces are a thing. Unfortunately, Double Method Interfaces have not attained thing status, and there's no reason to think they will, because that would require convincing Brian Goetz that partial functions should be pretty and efficient, and we all know that partial functions are disgusting and unnecessary.

Where, then the idea come from? Not from Haskell, where the main guidance is on how to avoid them. Not from Python, where - bless their hearts - they seem to have confused the idea with currying. Not Idris, whose dependent type system largely avoids the problem by checking the domain at compile time. We have something over Idris, in that Scala could deal better with - I don't know - a function defined only where the Riemann zeta function is less than 1/2.

(Pause briefly while you imagine me hunting for 30 minutes for just the right illustration and then deciding it was silly.)

TL;DR

When you're tempted to write a partial function, write a function that returns Option instead.

Here's what Chaucer has to say on the subject:

O blisful God, that art so Iust and trewe!
Lo, how that thou biwreyest mordre alway!
Parcial fonctione wol out, that see we day by day.
Parcial fonctione is so wlatsom and abhominable
To God, that is so Iust and resonable,

- The Nun's Priest's Tale


Comments

comments powered by Disqus