Failure should probably extend Try[Nothing], as shown in first lecture of Principles of Reactive Programming

209 views
Skip to first unread message

Christoph Neijenhuis

unread,
May 12, 2015, 11:34:48 AM5/12/15
to scala-l...@googlegroups.com
In the lecture Monads (slide 22), Try with Success and Failure was introduced. In particular, Failure was defined as:

case class Failure(ex: Exception) extends Try[Nothing]

However, in scala.util it is actually defined this way:

final case class Failure[+T](exception: Throwable) extends Try[T]

I tried to figure out why Failure would remain a generic class while e.g. None doesn't, but I couldn't come up with an explanation. In fact, I'd argue Failure, like None, should use the bottom type for two reasons:

1. When dealing with a Failure, one doesn't have to worry about a generic type that doesn't make any difference anyway. E.g. when pattern matching:

case None => ... // Compiles
case Failure => ... // Does not compile: "pattern type is incompatible with expected type"
case Failure[_] => ... // Compiles, but is unintuitive when working with None previously

Or when passing a failure along, one has to cast to the "correct" generic type (seen in the implementation of Failure itself, but this example is from the implementation of Future)

case f: Failure[_] => p complete f.asInstanceOf[Failure[(T, U)]]

When using the bottom type, this simply becomes:

case f: Failure => p complete f

2. The method signatures and the generated scaladoc are more obvious. E.g. the get method of None is defined as:

def get: Nothing

whereas the get method of Failure is:

def get: T

I'd argue in the case of None, it's much easier to figure out one shouldn't use the get method based on the signature.


I did go forward and changed the implementation of Failure: https://github.com/cneijenhuis/scala/commit/e03c6bda9d6c92b764f540278d718098e7778791#diff-a2cc47b875d07181ae9e71681fb3f07dL211
But the resulting class is obviously not compatible with the previous version. I fixed the resulting errors in Future and JavapClass as well - these changes also show nicely why I think it's benificial to use Nothing.

Anyway, my questions are:
Is there a reason I missed why Failure should really be a generic class and not extend Try[Nothing]?
If not - is there any chance I can submit a pull request with this? After all, it's a breaking change... but I do see those are scheduled for "Aida", and, while Try isn't a collection, this change would fit with the theme of "we want to make them even easier to use" :-)

Best,
Christoph

Viktor Klang

unread,
May 12, 2015, 1:19:38 PM5/12/15
to scala-l...@googlegroups.com

Hi Christoph,

The only reason this hasn't been done before is due to backwards compatibility.

--
Cheers,

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Rex Kerr

unread,
May 12, 2015, 2:02:32 PM5/12/15
to scala-l...@googlegroups.com
I basically agree with you, but note that there are three concepts that are useful:
  1. Try[A]  --  I might have an A, or I might have a failure
  2. Failure[A]  --  I tried to get an A, but I have a failure instead
  3. Failure  --  I don't know or care what I tried to get, but I know it didn't work

The current implementation gives you 1. and 2., but not 3.; I agree, especially given the covariance of Try and the methods on Try, that 1. and 3. are the more useful pair.  But occasionally it's nice to know what the success type should have been (e.g. because you want to dispatch on it with a type class), even though you're in the failure case.  (Refactoring is possible, but it shifts work from the compiler to the programmer.)

So I agree that the other choice would have been better, but there is at least _some_ advantage to the way it is.

  --Rex



--

Naftoli Gugenheim

unread,
May 12, 2015, 3:21:17 PM5/12/15
to scala-l...@googlegroups.com
Can you give an example of 2?

Rex Kerr

unread,
May 12, 2015, 5:21:01 PM5/12/15
to scala-l...@googlegroups.com
Failure right now is an example of #2.

  scala> val fail = scala.util.Failure[Long](new Exception)
  fail: scala.util.Failure[Long] = Failure(java.lang.Exception)

This can be handy because when you have methods like

  def orDefault[A](t: Try[A]) = t match { case Success(a) => a; case _ => null.asInstanceOf[A] }

you can pass `fail` right in without having to specify what the default type was supposed to be:

  scala> orDefault(fail)
  res2: Long = 0

This is less useful, I think, than not having to worry about the correct type when you already know you're in the wrong branch, but it's not completely useless.

(It would be even more useful if there were more easy ways to end up with a properly typed Failure.  After all, this really should work; the compiler knows everything it needs to so that it does:

  Try(2) match {
    case s: Success => 0
    case f: Failure => orDefault(f)
  }

but alas, it insists on making you fill in the [Int] for both Success and Failure (though it's happy to do it for you on the unapply).)

  --Rex

Christoph Neijenhuis

unread,
May 13, 2015, 5:57:37 AM5/13/15
to scala-l...@googlegroups.com
Hi Viktor,

thanks, I guess that makes sense. The roadmap mentions an automatic migration tool (a la go fix for Go) is planned - maybe this change can be reconsidered when the work starts on that one?

Christoph Neijenhuis

unread,
May 13, 2015, 6:34:31 AM5/13/15
to scala-l...@googlegroups.com
Rex, you definitely make a valid point. In some cases where you want concept 2. but get an untyped class, the compiler is still able to infer the correct class. E.g. (I'm using Option here instead of Try, as None is an instance of concept 3.):

scala> def orDefault[A](o: Option[A]) = o match { case Some(a) => a; case _ => null.asInstanceOf[A] }
orDefault
: [A](o: Option[A])A

scala
> val opt: Option[Int] = None
opt
: Option[Int] = None

scala
> orDefault(opt)
res0
: Int = 0 // Got it because we specified Option[Int]

scala
> val opt3 = None
opt3
: None.type = None

scala
> val res: Int = orDefault(opt3)
res
: Int = 0 // Got it because we specified our expected class

In other instances, the compiler can't do anything, e.g.

scala> orDefault(opt3)
java
.lang.NullPointerException
...

I hoped it would work fine in the "normal" use case for concept 2., which is IMO using orElse/getOrElse or recover (for a Try). Your example rewritten with the use of getOrElse:

scala> def default[A]() = null.asInstanceOf[A]
default: [A]()A

scala
> opt.getOrElse(default())
java
.lang.NullPointerException // Compiler can't infer Int
...

scala
> opt.getOrElse(default[Int]()) // Manually specifing the default type works...
res8
: Int = 0

The problem is that getOrElse (and recover in Try as well) do not necessarily return the same type (like your "orDefault" function), but a supertype:

def getOrElse[B >: A](default: => B): B

// This can btw lead to unexpected things like:
scala
> Some(1L) getOrElse(0)
res5
: AnyVal = 1 // Note the type isn't Long, but AnyVal

...and therefor the compiler can't infer the correct type with getOrElse, whereas that was possible with your "orDefault" function.

Thanks for bringing this up - it didn't occur to me on my own :)

Rich Dougherty

unread,
May 13, 2015, 11:42:19 PM5/13/15
to scala-l...@googlegroups.com
There doesn't seem to be much that can be done about the issue without causing compatibility problems, but here's the issue I created last year:


Rex, for your 3 cases there's a good analogy with Option: None is an Option[Nothing], but if I want a None that's an Option[A] I'll often use Option.empty[A]. In a similar way you could have Failure as a Try[Nothing] and a method Try.failure[A] that returns a Failure as a Try[A].

– Rich

On Wed, May 13, 2015 at 5:19 AM, Viktor Klang <viktor...@gmail.com> wrote:

Hi Christoph,

The only reason this hasn't been done before is due to backwards compatibility.

--
Rich Dougherty
Engineer, Typesafe, Inc

Simon Ochsenreither

unread,
May 14, 2015, 5:33:11 PM5/14/15
to scala-l...@googlegroups.com, ri...@rd.gen.nz
I agree! I think we could tackle this as soon as those source-rewriting tools are finally announced as available and stable...

Simon Schäfer

unread,
May 14, 2015, 6:33:30 PM5/14/15
to scala-l...@googlegroups.com


On 05/14/2015 11:33 PM, Simon Ochsenreither wrote:
> I agree! I think we could tackle this as soon as those
> source-rewriting tools are finally announced as available and stable...
I wonder: Has anyone even started working on such a tool?

Samuel Grütter

unread,
May 15, 2015, 3:15:34 AM5/15/15
to scala-l...@googlegroups.com

Yes, I have, see https://github.com/samuelgruetter/srewrite

It's still in a very early phase, but we already used it to import the tests from Scala 2 into dotty (see PRs https://github.com/lampepfl/dotty/pull/294 and https://github.com/lampepfl/dotty/pull/558).

So far it only makes changes required by differences in the language, but it could easily be extended to also make changes required by differences in the library.

 

Eugene Burmako

unread,
May 15, 2015, 3:22:20 AM5/15/15
to scala-l...@googlegroups.com
We're also applying final touches to scala.meta to make it fully applicable to these kinds of tasks. If someone would be interested in lending a hand, please let me know, and I'll elaborate. 

Simon Schäfer

unread,
May 15, 2015, 4:19:26 AM5/15/15
to scala-l...@googlegroups.com


On 05/15/2015 09:22 AM, Eugene Burmako wrote:
We're also applying final touches to scala.meta to make it fully applicable to these kinds of tasks. If someone would be interested in lending a hand, please let me know, and I'll elaborate.
I'm surprised that srewrite is based on scalac and not already on dotc given that scalac is completely incapable of handling any source code rewritings. In the IDE we use scalac's trees everywhere and we only have problems with it.

If srewrite goal was and still is to be a converter for small language changes, whose scope is extremely small, it may work well. But no one should expect to get a correct and easy to use source rewriting tool users can use in a real world environment.

On Friday, May 15, 2015 at 9:15:34 AM UTC+2, Samuel Grütter wrote:

On Friday, May 15, 2015 at 12:33:30 AM UTC+2, Simon Schäfer wrote:
On 05/14/2015 11:33 PM, Simon Ochsenreither wrote:
> I agree! I think we could tackle this as soon as those
> source-rewriting tools are finally announced as available and stable...
I wonder: Has anyone even started working on such a tool?

Yes, I have, see https://github.com/samuelgruetter/srewrite

It's still in a very early phase, but we already used it to import the tests from Scala 2 into dotty (see PRs https://github.com/lampepfl/dotty/pull/294 and https://github.com/lampepfl/dotty/pull/558).

So far it only makes changes required by differences in the language, but it could easily be extended to also make changes required by differences in the library.

 
--

Christoph Neijenhuis

unread,
May 15, 2015, 4:27:35 AM5/15/15
to scala-l...@googlegroups.com, ri...@rd.gen.nz

On Thursday, May 14, 2015 at 5:42:19 AM UTC+2, Rich Dougherty wrote:
There doesn't seem to be much that can be done about the issue without causing compatibility problems, but here's the issue I created last year:



Hey Rich, I tried to google some previous discussion of this, but failed (try and nothing aren't particularly good terms for google ;) ). Thanks for pointing it out!

Mirko Stocker

unread,
May 15, 2015, 4:35:31 AM5/15/15
to scala-l...@googlegroups.com
On Friday 15 May 2015 00.15:34 Samuel Grütter wrote:
> Yes, I have, see https://github.com/samuelgruetter/srewrite

Oh, interesting. I wonder if you've looked at scala-refactoring [1]? I know
it's not perfect, but it already has tons of workarounds for incorrect
positions and some desugaring of scalac trees (e.g. [2]).

Cheers

Mirko

[1] https://github.com/scala-ide/scala-refactoring
[2] https://github.com/scala-ide/scala-refactoring/blob/master/org.scala-refactoring.library/src/main/scala/scala/tools/refactoring/common/PimpedTrees.scala

--
Mirko Stocker | mi...@stocker.email
Work: http://ifs.hsr.ch | http://infoq.com
Personal: http://misto.ch | http://twitter.com/m_st

Rex Kerr

unread,
May 15, 2015, 4:42:23 AM5/15/15
to scala-l...@googlegroups.com
But the converter has to be based on scalac, because it has to parse Scala 2.11 code, and dotc doesn't do that.  If you wanted to write a dotty-to-2.11 converter, you'd write it for dotc.

(Or, that's what you have to do to avoid reinventing a very sizable portion of the tree generation routine.)

  --Rex

Samuel Grütter

unread,
May 15, 2015, 11:28:40 AM5/15/15
to scala-l...@googlegroups.com
On Fri, May 15, 2015 at 10:42 AM, Rex Kerr <ich...@gmail.com> wrote:
>
> But the converter has to be based on scalac, because it has to parse Scala 2.11 code, and dotc doesn't do that.
>

Exactly.

>
> On Fri, May 15, 2015 at 1:19 AM, Simon Schäfer <ma...@antoras.de> wrote:
>>
>>
>> In the IDE we use scalac's trees everywhere and we only have problems with it.
>>


On Fri, May 15, 2015 at 10:35 AM, Mirko Stocker <mi...@stocker.email> wrote:
> Oh, interesting. I wonder if you've looked at scala-refactoring [1]? I know
> it's not perfect, but it already has tons of workarounds for incorrect
> positions and some desugaring of scalac trees (e.g. [2]).

I've also had problems with scalac's trees, but I hope that scala.meta
will soon provide a nice and correct API that can be used for such
transformations/rewriting. That's also why I did not yet use another
library.
Reply all
Reply to author
Forward
0 new messages