SIP-14, Futures, Try, Either and Option

1,383 views
Skip to first unread message

Daniel Sobral

unread,
Oct 12, 2012, 6:46:06 PM10/12/12
to scala...@googlegroups.com
Following up on my experience with Dispatch 0.9.x's Promise, which is
pretty much like SIP-14's Future, there's something that's bothering
me about Future and Try: it's not dead-easy to transform a Future[T]
into a Future[Option[T]] or Future[Either[Exception, T]] as a way of
handling exceptions that may have occurred, and it's hard to convert a
Try[T] into a Try[Either[Exception, T]] as well.

I've used these features a lot, and I'm wondering if there's something
I'm missing, or if they are something Future/Try are missing.

--
Daniel C. Sobral

I travel to the future all the time.

Alex Repain

unread,
Oct 12, 2012, 6:58:42 PM10/12/12
to scala...@googlegroups.com


2012/10/13 Daniel Sobral <dcso...@gmail.com>

Following up on my experience with Dispatch 0.9.x's Promise, which is
pretty much like SIP-14's Future, there's something that's bothering
me about Future and Try: it's not dead-easy to transform a Future[T]
into a Future[Option[T]] or Future[Either[Exception, T]] as a way of
handling exceptions that may have occurred, and it's hard to convert a
Try[T] into a Try[Either[Exception, T]] as well.
 
I've used these features a lot, and I'm wondering if there's something
I'm missing, or if they are something Future/Try are missing.

If I recall correctly, there exists a Future monad, and an Option monad. I'm no monad expert (yet ! :), but maybe there is a monad composition trick that could automatize these transformations, if you define your futures as a monad instance ?
 

Rex Kerr

unread,
Oct 12, 2012, 6:58:51 PM10/12/12
to scala...@googlegroups.com
On Fri, Oct 12, 2012 at 6:46 PM, Daniel Sobral <dcso...@gmail.com> wrote:
and it's hard to convert a
Try[T] into a Try[Either[Exception, T]] as well.

What's the use case?  Try itself has the functionality of Either, and is bulletproof on all operations, so it's not really clear what one gains with an extra layer of wrapping.  .toEither gives you the Either[Throwable,T] when you're done with the dangerous stuff.

I'm with you on Future, though.  (Though I'd think it should be Future[T] -> Future[Try[T]].)

  --Rex

Daniel Sobral

unread,
Oct 12, 2012, 7:11:43 PM10/12/12
to scala...@googlegroups.com
On Fri, Oct 12, 2012 at 7:58 PM, Rex Kerr <ich...@gmail.com> wrote:
> On Fri, Oct 12, 2012 at 6:46 PM, Daniel Sobral <dcso...@gmail.com> wrote:
>>
>> and it's hard to convert a
>> Try[T] into a Try[Either[Exception, T]] as well.
>
>
> What's the use case? Try itself has the functionality of Either, and is
> bulletproof on all operations, so it's not really clear what one gains with
> an extra layer of wrapping. .toEither gives you the Either[Throwable,T]
> when you're done with the dangerous stuff.

What toEither? It's not on scaladoc, and I can't find it in the source
of 2.10.0-wip, 2.10.x or master (unless it's inherited). For Try, I'd
*like* to have toEither, just as it has toOption.

> I'm with you on Future, though. (Though I'd think it should be Future[T] ->
> Future[Try[T]].)

Well, that would work too, since I can pattern match on a Try. I still
want Option, though, since it has an implicit conversion into
Iterable, making it easier to work on for comprehensions.

Kevin Wright

unread,
Oct 12, 2012, 7:21:46 PM10/12/12
to scala...@googlegroups.com
It's not *too* hard though, doing this from memory (so please excuse any errors):

def asFutureEither[T](f: Future[T]): Future[Either[Exception, T]] =
  f map { Right(_) } recover { case x => Left(x) }

stick in one of them there new-fangled implicit classes to enrich all your futures, and you should be sorted!

Daniel Sobral

unread,
Oct 12, 2012, 7:30:15 PM10/12/12
to scala...@googlegroups.com
On Fri, Oct 12, 2012 at 8:21 PM, Kevin Wright <kev.lee...@gmail.com> wrote:
> It's not *too* hard though, doing this from memory (so please excuse any
> errors):
>
> def asFutureEither[T](f: Future[T]): Future[Either[Exception, T]] =
> f map { Right(_) } recover { case x => Left(x) }
>
>
> stick in one of them there new-fangled implicit classes to enrich all your
> futures, and you should be sorted!

Two methods calls, a function being passed to each of them -- that
counts as "too hard" in my book compared to ".either" that Dispatch
gives me. :-)

This is Scala we are talking about, were the hard things are one
liners, and the impossible ones may take half a dozen!

√iktor Ҡlang

unread,
Oct 13, 2012, 7:57:08 AM10/13/12
to scala...@googlegroups.com
On Sat, Oct 13, 2012 at 12:46 AM, Daniel Sobral <dcso...@gmail.com> wrote:
Following up on my experience with Dispatch 0.9.x's Promise, which is
pretty much like SIP-14's Future, there's something that's bothering
me about Future and Try: it's not dead-easy to transform a Future[T]
into a Future[Option[T]] or Future[Either[Exception, T]] as a way of
handling exceptions that may have occurred, and it's hard to convert a
Try[T] into a Try[Either[Exception, T]] as well.

I've used these features a lot, and I'm wondering if there's something
I'm missing, or if they are something Future/Try are missing.

Could you please enumerate the actual use-cases (should be simple enough if you've used them a lot)?

I'm quite sure Futures cover all the use-cases, so give me your best shot!

Cheers,
 

--
Daniel C. Sobral

I travel to the future all the time.



--
Viktor Klang

Akka Tech Lead
Typesafe - The software stack for applications that scale

Twitter: @viktorklang

Rex Kerr

unread,
Oct 14, 2012, 7:41:32 AM10/14/12
to scala...@googlegroups.com
Ack, there's no toEither _and_ no fold?

ugh.transform(t => Try(Right(t)), f => Try(Left(f))).get is such a mouthful.

I guess this is one of those "time for everyone to write the same method and put it in their personal library" moments since RC1 is upon us.  I wish I'd noticed this earlier.

All together now:

  import scala.util.{Try, Failure, Success}
  implicit class TryHarder[T](t: Try[T]) {
    def toEither: Either[Throwable,T] = t match {
      case Success(s) => Right(s)
      case Failure(f) => Left(f)
    }
    def fold[U](f: T => U, g: Throwable => U) = toEither.fold(g,f)
  }

Also, without this Try is not super-easy to use unless you import Success and Failure also, but the tempting import scala.util._ gives you DynamicVariable, Marshal, MurmurHash, Properties, Random, Sorting, automata, continuations, control, grammar, hashing, logging, matching, parsing, and regexp in your namespace also.  Yay?

  --Rex

Heather Miller

unread,
Oct 14, 2012, 8:13:10 AM10/14/12
to scala...@googlegroups.com
The location and thus the inconvenient import was brought up back in May: https://groups.google.com/d/msg/scala-internals/lrsgVkH3OZU/N0HpvmPhpTcJ
Unfortunately, there was no counter-proposal about where to put it back then, so it remained in util. It doesn't seem appropriate to put it in package scala nor to alias it there.

Regarding `toEither`- I think it's appropriate and rather symmetric given the existence of `toOption`. I think it's something we can definitely add to 2.10.1, which hits the shelves in four short months, so all is not lost.

--
Heather Miller
Doctoral Assistant
EPFL, IC, LAMP

√iktor Ҡlang

unread,
Oct 14, 2012, 8:15:26 AM10/14/12
to scala...@googlegroups.com
On Sun, Oct 14, 2012 at 1:41 PM, Rex Kerr <ich...@gmail.com> wrote:
Ack, there's no toEither _and_ no fold?

ugh.transform(t => Try(Right(t)), f => Try(Left(f))).get is such a mouthful.

In this case I'd say the problem is that transform has a suboptimal signature:

deftransform[U](s: (T) ⇒ Try[U]f: (Throwable) ⇒ Try[U])Try[U] 


IMO it would be:

def transform[U](s: T => U, f: Throwable => U): Try[U]

Then "fold" would be: t.transform(Left(_), Right(_)).get //the ".get" is for getOrThrow semantics


I guess this is one of those "time for everyone to write the same method and put it in their personal library" moments since RC1 is upon us.  I wish I'd noticed this earlier. 

Well, I'd argue that adding "toX" methods is an antipattern, and the inclusion of "to" on collections in 2.10 is a way to get out of that antipattern situation.
 

All together now:

  import scala.util.{Try, Failure, Success}
  implicit class TryHarder[T](t: Try[T]) {
    def toEither: Either[Throwable,T] = t match {
      case Success(s) => Right(s)
      case Failure(f) => Left(f)
    }
    def fold[U](f: T => U, g: Throwable => U) = toEither.fold(g,f)
  }

Also, without this Try is not super-easy to use unless you import Success and Failure also, but the tempting import scala.util._ gives you DynamicVariable, Marshal, MurmurHash, Properties, Random, Sorting, automata, continuations, control, grammar, hashing, logging, matching, parsing, and regexp in your namespace also.  Yay?

Not yay.

Cheers,
√ 
 


  --Rex


On Fri, Oct 12, 2012 at 7:11 PM, Daniel Sobral <dcso...@gmail.com> wrote:
On Fri, Oct 12, 2012 at 7:58 PM, Rex Kerr <ich...@gmail.com> wrote:
> On Fri, Oct 12, 2012 at 6:46 PM, Daniel Sobral <dcso...@gmail.com> wrote:
>>
>> and it's hard to convert a
>> Try[T] into a Try[Either[Exception, T]] as well.
>
>
> What's the use case?  Try itself has the functionality of Either, and is
> bulletproof on all operations, so it's not really clear what one gains with
> an extra layer of wrapping.  .toEither gives you the Either[Throwable,T]
> when you're done with the dangerous stuff.

What toEither? It's not on scaladoc, and I can't find it in the source
of 2.10.0-wip, 2.10.x or master (unless it's inherited). For Try, I'd
*like* to have toEither, just as it has toOption.

> I'm with you on Future, though.  (Though I'd think it should be Future[T] ->
> Future[Try[T]].)

Well, that would work too, since I can pattern match on a Try. I still
want Option, though, since it has an implicit conversion into
Iterable, making it easier to work on for comprehensions.


--
Daniel C. Sobral

I travel to the future all the time.

√iktor Ҡlang

unread,
Oct 14, 2012, 8:24:57 AM10/14/12
to scala...@googlegroups.com
On Sun, Oct 14, 2012 at 2:15 PM, √iktor Ҡlang <viktor...@gmail.com> wrote:


On Sun, Oct 14, 2012 at 1:41 PM, Rex Kerr <ich...@gmail.com> wrote:
Ack, there's no toEither _and_ no fold?

ugh.transform(t => Try(Right(t)), f => Try(Left(f))).get is such a mouthful.

In this case I'd say the problem is that transform has a suboptimal signature:

deftransform[U](s: (T) ⇒ Try[U]f: (Throwable) ⇒ Try[U])Try[U] 


IMO it would be:

def transform[U](s: T => U, f: Throwable => U): Try[U]

Then "fold" would be: t.transform(Left(_), Right(_)).get //the ".get" is for getOrThrow semantics

The idea here is to throw an exception in the transform to go to failure and to return a value of type U to go to Success.
 In the case of a rethrow (for the failure branch) that should be very fast.

Rex Kerr

unread,
Oct 14, 2012, 11:48:31 AM10/14/12
to scala...@googlegroups.com
On Sun, Oct 14, 2012 at 8:24 AM, √iktor Ҡlang <viktor...@gmail.com> wrote:


On Sun, Oct 14, 2012 at 2:15 PM, √iktor Ҡlang <viktor...@gmail.com> wrote:


On Sun, Oct 14, 2012 at 1:41 PM, Rex Kerr <ich...@gmail.com> wrote:
Ack, there's no toEither _and_ no fold?

ugh.transform(t => Try(Right(t)), f => Try(Left(f))).get is such a mouthful.

In this case I'd say the problem is that transform has a suboptimal signature:

deftransform[U](s: (T) ⇒ Try[U]f: (Throwable) ⇒ Try[U])Try[U] 


IMO it would be:

def transform[U](s: T => U, f: Throwable => U): Try[U]

Then "fold" would be: t.transform(Left(_), Right(_)).get //the ".get" is for getOrThrow semantics

The idea here is to throw an exception in the transform to go to failure and to return a value of type U to go to Success.
 In the case of a rethrow (for the failure branch) that should be very fast.

Well, there are three type signatures that might be useful

(1) "dual flatMap"
  transform[U](s: T => Try[U], f: Throwable => Try[U]): Try[U]

(2) "dual map"

  transform[U](s: T => U, f: Throwable => U): Try[U]

(3) "fold"
  fold[U](s: T => U, f: Throwable => U): U

Without the bulletproof property (I seem to be the only one using this term, which I use to mean that for every method of  Try that returns a Try, that method will never throw a NonFatal exception but will instead package it), (1) is arguably at least as desirable as (2), since you probably want to stay exception-safe, so you'll be wrapping in Try anyway, and this will save you flattening boilerplate.

With the bulletproof property, (1) is a somewhat exotic case, and (2) is the desired signature.

Neither can really substitute for (3), though you can use (2) and then get if you must for (3).  (Note that (1) can be implemented fold(s,f) and (2) can be implemented as fold(x => Try(s(x)),t => Try(f(t))), as long as you don't need to worry about bulletproofness--(2) works even then but (1) fails and has to be Try(fold(s,f)).flatten.)

The original drafts of Try were not robustly bulletproof, so I suspect that the original design wasn't intending for that to be a core property.  My guess is the signature of transform didn't get updated whenever the change happened (if it happened--I'm speculating).

Anyway, this is a long way of saying I agree regarding the signature as long as we don't do that instead of fold.

  --Rex

Daniel Sobral

unread,
Oct 15, 2012, 8:34:13 AM10/15/12
to scala...@googlegroups.com
On Sat, Oct 13, 2012 at 8:57 AM, √iktor Ҡlang <viktor...@gmail.com> wrote:
>
>
> On Sat, Oct 13, 2012 at 12:46 AM, Daniel Sobral <dcso...@gmail.com> wrote:
>>
>> Following up on my experience with Dispatch 0.9.x's Promise, which is
>> pretty much like SIP-14's Future, there's something that's bothering
>> me about Future and Try: it's not dead-easy to transform a Future[T]
>> into a Future[Option[T]] or Future[Either[Exception, T]] as a way of
>> handling exceptions that may have occurred, and it's hard to convert a
>> Try[T] into a Try[Either[Exception, T]] as well.
>>
>> I've used these features a lot, and I'm wondering if there's something
>> I'm missing, or if they are something Future/Try are missing.
>
>
> Could you please enumerate the actual use-cases (should be simple enough if
> you've used them a lot)?
>
> I'm quite sure Futures cover all the use-cases, so give me your best shot!

Ok, let's see. First, either.

This one transforms turns an error result into an empty string, which
is a good enough default for the upstream, plus log errors.

Http(request.HEAD OK header("Last-Modified")).either map {
case Right(something) => something
case Left(ex) =>
logger error "%s: erro ao verificar data de alteração:
%s".format(request.build().getUrl, ex)
""
}

And this one turns an error result into a None (the successful result
is an Option), and log errors.

val httpResponse = Http(request.GET OK header("Last-Modified")).either
httpResponse.map {
case Right(something) => something
case Left(ex) =>
logger error "%s: erro no download:
%s".format(request.build().getUrl, ex)
None
}.apply()

This is similar to the above, except that the the success is not an
Option (pun intended):

(db consulta DBInfo("monitoramento", "monitorado",
None)).either.apply() match {
case Right(resultado) => Some(parse(resultado))
case Left(ex) => logger error "Erro ao obter municípios
monitorados: %s".format(ex.getMessage); None

The next, and final, two just produces different logging messages
based on success/failure:

val respostas = urls map { cockpitUrl =>
cockpitUrl ->
Http(url(cockpitUrl).POST.setBody(json).setHeader("content-type",
"application/json") OK as.String).either
}
respostas foreach {
case (cockpitUrl, resposta) =>

resposta foreach {
case Left(ex) => // TODO: retry?
logger error "%s: erro na notificação: %s (ids perdidos:
%s)".format(cockpitUrl, ex, ids mkString ", ")
case _ =>
logger trace "Notificado %s com %s".format(cockpitUrl, ids
mkString ", ")
}
}

val resultadoDaPersistencia = (db insere (dbInfo, dadosAGravar)).either

resultadoDaPersistencia foreach {
case Right(_) =>
logger trace "Persistido %s/%s/%s".format(dbInfo.indice,
dbInfo.tipo, dbInfo.id getOrElse "")
case Left(ex) =>
logger error "Falha ao persistir
%s/%s/%s!".format(dbInfo.indice, dbInfo.tipo, dbInfo.id getOrElse "")
}

As for ".option", I have a single use outside tests, where I just
don't care whether I got an error or not:

def consultaNode(hostname: String): Option[String] =
http(host(hostname, portaDoElasticSearch) / "_cluster" / "nodes"
OK as.String).option.apply()

I had more of those, but they progressively because ".either", to
produce logging.

Daniel Sobral

unread,
Nov 6, 2012, 2:58:59 PM11/6/12
to scala...@googlegroups.com
So... how do I do that stuff with Future/Promise?
Reply all
Reply to author
Forward
Message has been deleted
0 new messages