Saturday, November 7, 2015

Simple, obvious, elegant

I am teaching a Scala for Big Data class for graduate engineering students. As we have been getting into the compositional and pure functional aspects of Scala, I've been trying to help the students solve problems. I have a mantra for these situations: Simple, obvious, elegant (SOE for short). Let's look at an example:

Let's say we are looking for a method which, given a function of type A=>B, would return a function of type F[A]=>F[B] where F is a container of some sort, such as Option, Seq, Try, etc. In particular, we will require that F be a functor because it is obviously going to require the map method. In other words we need a body for the following signature:
def lift[A,B](f: A => B): F[A] => F[B] = ???
What could possibly go in the body? Well, what information do we have? We know about f. That's it! But somehow, we need this to form a function which will operate on the content of the container. That's to say, we will need map. What will the map method operate on? Whatever is in context when it is invoked, something we don't know yet. That sounds like we need a partially applied function. But this time it is the receiver* of the method, not a parameter that is unknown. Therefore the only possible answer that satisfies our requirements (the signature) is:
def lift[A,B](f: A => B): F[A] => F[B] = _ map f
In particular, we can define the following methods, and many others, inside their respective classes/traits:
def liftOption[A,B](f: A => B): Option[A] => Option[B] = _ map f
def liftSeq[A,B](f: A => B): Seq[A] => Seq[B] = _ map f
Here's another example. I've been working on a type of number that is lazily evaluated when a function is applied to it. This will form part of a fuzzy number suite that I'm developing. I could perhaps use Real from Spire but for now I want to keep it simple without bringing in any external libraries. To begin with, my lazy number class is defined thus:
abstract class LazyNumber[X : Fractional](x: X, f: X=>X) extends Valuable[X] with Fractional[LazyNumber[X]] {
  def get = f(x)
  def construct(x: X, f: X=>X): LazyNumber[X]
  def map(g: X=>X): LazyNumber[X] = construct(x,g.compose(f)) 
...}
where Valuable defines the (abstract) get method, construct is implemented by sub-classes (we could call it unit if we liked). So, the value of a lazy number is f(x) and applying a function g to this lazy number consists of composing function g with f and leaving x as is.  What about flatMap? We'd like to be able to construct for-comprehensions with our lazy numbers, wouldn't we? So, we need a body for the following signature:
def flatMap(g: X=>LazyNumber[X]): LazyNumber[X] = ???
Again, what could possibly go in the body? Well, what do we have? We've got x, f and g. What would be simple, obvious and elegant? How about:
def flatMap(g: X=>LazyNumber[X]): LazyNumber[X] = g(f(x))
Could anything possibly be more obvious, simple or elegant? 

* We could easily get into a debate here: receiver is really an artifact of object-oriented programming -- from a functional programming point of view, the two parameters to the map function (the pre-parameter and the post-parameter) are just parameters.

No comments:

Post a Comment