Re: Failure should probably extend Try[Nothing], as shown in first lecture of Principles of Reactive Programming
Christoph Neijenhuis <[email protected]>
| Newsgroups | gmane.comp.lang.scala |
|---|---|
| Message-ID | <[email protected]> |
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 :)
On Tuesday, May 12, 2015 at 11:21:01 PM UTC+2, Rex Kerr wrote:
>
> 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
>
>
> On Tue, May 12, 2015 at 12:21 PM, Naftoli Gugenheim <[email protected]
> <javascript:>> wrote:
>
>> Can you give an example of 2?
>>
>> On Tue, May 12, 2015 at 2:02 PM Rex Kerr <[email protected] <javascript:>>
>> wrote:
>>
>>> 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
>>>
>>>
>>>
>>> On Tue, May 12, 2015 at 8:34 AM, Christoph Neijenhuis <
>>> [email protected] <javascript:>> wrote:
>>>
>>>> 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
>>>>
>>>> --
>>>> 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 [email protected] <javascript:>.
>>>> For more options, visit https://groups.google.com/d/optout.
>>>>
>>>
>>> --
>>> 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 [email protected] <javascript:>.
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>> --
>> 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 [email protected] <javascript:>.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
>
--
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 [email protected].
For more options, visit https://groups.google.com/d/optout.