Thursday 14 June 2012

Fixing scala.Either - unbiased vs biased

[2012-06-18 Update: details of new branch added, where Either gains right-biased capability while retaining its unbiased one.]
[2012-06-25 Update: details of new branch added, where Either gains a withFilter method.]
[2012-06-29 Update: link to SIP candidate added.]

This latest effort is the result of a stalled attempt to write a formal proposal (SIP) explaining exactly how scala.Either should be fixed.

It became apparent that the need to first deprecate things, as opposed to simply removing them, might make the approach I was advocating less attractive.

So, with a view to helping the community to come to an informed decision, I now present a workable set of changes that leaves scala.Either unbiased, together with one that gives it a bias towards its Right subtype, for comparison.

Unbiased

To recap, we wish to make the following changes to Either's Left-/RightProjection inner classes:
  • have map return a Left-/RightProjection instead of an Either
  • have flatMap also do this, and also take an A => Left-/RightProjection instead of an A => Either
  • remove filter
Although we can deprecate filter, we cannot do this to the existing versions of its map and flatMap.

Therefore, we have no choice but to deprecate Left-/RightProjection themselves, and in turn, Either's left and right methods.

The alternative names I came up with are Left-/RightProj, lp and rp. At least they are shorter, and anyway, left and right would no longer be correct, since their map and flatMap now return a Left-/RightProj instead of an Either.

One advantage of this is that we can now just leave out filter, rather than have to deprecate it.

Right-Biased

Here we need to do the following:
  • deprecate Either's left and right methods
  • deprecate Left-/RightProjection themselves
  • add the following methods to Either itself
    • get
    • getOrElse
    • foreach
    • forall
    • exists
    • flatMap
    • map
    • toSeq
    • toOption
Note the absence of filter.

Decision time

Please take a look at the unbiased (lp_rp branch) and Right-biased (right-biased branch) end results, together with the respective versions of the accompanying test code:

The reasons to remain unbiased, which occur to me, are as follows,
  • any use-cases demanding the ability to choose from which subtype (Left or Right) the value passed to map, etc. is taken, will not be excluded
  • Either does not imply any bias, although Left and Right do imply a weak one

while those to go biased, would seem to me to be that,
  • there is no need to append .rp (or .lp) in for comprehensions, which must be done for each generator
  • there is no need to append .e in order to obtain the Either from the result yielded.
Please register your preference on this scala-debate thread, or else below.

Thanks!

2012-06-18 Following a highly productive exchange of ideas on the above thread, a new branch entitled add_right-bias_2-10 has been added to the project. In this version of Either, the question of unbiased vs biased goes away! Since these two usage modes turn out not to be mutually exclusive, a right-bias has been added, and the existing unbiased capability (through the lp and rp methods) retained.

2012-06-25 add_right-bias_2-10_withFilter branch added to the project. This version adds a withFilter method to Either, such that right-biased for comprehensions may now use if and involve refutable pattern-matching, provided the appropriate implicit conversions are supplied (as indicated by the compiler), that will be used to create a Left value whenever the expression following if is false, or there is no match.

2012-06-29 SIP candidate now written.

Thursday 7 June 2012

Okay/Reason extends Checked (a biased scala.Either)

[2012-06-08 Update: added link to 'tests involving Option', demonstrating how 'if' may still be used in for-comprehensions involving Checked.]
[2012-06-09 Update: last four 'tests involving Option' now demonstrate converting from a Checked[A, R] to a Checked[B, R].]
[2012-06-10 Update: the last eight 'tests involving Option' now demonstrate the above; links to Musicians and printMusicians test code added.]

This blog has so far been concerned with how to enhance the existing scala.Either class.

The first three posts presented certain 'extras' that can be used together with the unmodified Either,


while the previous one presented a modified version which claims to fix certain of its shortcomings.

Today's post, however, presents an alternative to Either, and is perhaps the culmination of the above.

Motivation and credits

The current effort is intended to help inform any decision to add an alternative to Either to the Scala library. It owes much to the original author of the Either class, Tony Morris, and to contributors to the debate referred to in the previous post, wherein other alternatives are discussed.

The 'biased alternative' to go alongside the unbiased Either envisaged in that post has now been written. What follows is a brief tutorial, followed by some instructions for using it yourself.

Tutorial

Checked[A, R] is for use as the return type, in place of some type, A, wherever an exception might otherwise be thrown and a more functional style is preferred. Such a result will contain either an A wrapped in an instance of Okay or else an R wrapped in an instance of Reason, depending on whether or not an exceptional condition arose.


Checked vs Either

There are three main differences.

Firstly, rather than have to specify that it's only the value contained in the Right (or perhaps, Left) instance of an Either that's to be passed to the foreach, map or flatMap methods, it is only ever the value contained in Okay instances of Checked, that is passed.


Secondly, Checked has no filter or withFilter method, and consequently, no support for if in for comprehensions, on the grounds that the result should never be empty - either it's Okay, or else there's a Reason why not. Note, however, that if may still be used following a conversion from a Checked to a scala.Option, which does have both these methods, as demonstrated here.

Thirdly, Checked comes complete with implicit support similar to that provided for Either by EitherExtras:
  • a.succeed becomes a.toOkay
  • a.fail    becomes a.toReason
  • a.fastCheck becomes a.ff (as in fail-fast)
  • a.slowCheck becomes a.fs (as in fail-slowly)
  • fast(f) <*> ... becomes ff(f) <*> ... where f is curried
  • slow(f) <*> ... becomes fs(f) <*> ... ditto

Mix-in or import

Checked, together with its implicit support, is nested in a trait called Checking. As is the case for EitherExtras, this has an abstract type, now called R, which must be set to the type of the value wrapped by Reason when using the <*> operator.

As is also the case for EitherExtras, Checking has a companion object which extends its companion trait, and sets the abstract type to String.

For example code, please see the following test code:

Instructions

To make use of the Scala 2.9.2-compatible class files now available in the Maven central repository, add the following to your build.sbt,

libraryDependencies += "org.lafros" %% "scala-checking" % "1.0"

or the following to your pom.xml.
<dependency>
  <groupId>org.lafros</groupId>
  <artifactId>scala-checking</artifactId>
  <version>1.0</version>
</dependency>
The code is available under the Apache v2.0 licence, and maintained in a Github project called scala-checking.

Comments welcome.