Exception/Try/Either guidelines?

514 views
Skip to first unread message

Patrick Roemer

unread,
Apr 2, 2015, 6:35:11 PM4/2/15
to scala...@googlegroups.com
Hi,

are there any recommendable guidelines available on how to tackle
exception handling in non-trivial projects? Or any (open source) code
bases I should take a look at in order to learn?

So far I've been rather sloppy about this - I basically used Java-style
exceptions in my own code until I hit a module (package) boundary, there
I'd convert to Try or Either. Or maybe not. If 3rd party libs would
issue exception, Try or Either, I'd carry them along until I had to
unify them with whatever I've been using as the "canonical" failure
representation.

Now I'd like to take a more unified approach. What I've found searching
the web was mostly basic (i.e. class level) examples, or it wasn't very
convincing - for example, the databricks style guide seems to recommend
Java style exceptions over Try, but illustrates this advice with an
example where the Try and exception versions simply aren't equivalent.

What's the recommended, idiomatic way of exception handling? Java
exceptions, Try all over the place, or a compromise like: Exceptions
within a certain scope (method, class, package) are ok but must be
converted to {Try, Either} at scope boundaries? Or something completely
different? How do you reconcile varying exception representations from
3rd party libs with yours?

I've tinkered a bit with the "Try all over the place" idea for a typical
JDBC query scenario, throwing in scala-arm to make it more interesting,
and the best I could come up with so far is this:

<snip>
private def execStatement[T](op: ResultSet => T)(statement:
PreparedStatement): Try[T] = {
for {
_ <- Try { statement.execute() }
resultSet <- ifNullFail(statement.getResultSet(), "result set")
result <- managed(resultSet).map(op).asTry
} yield result
}

def execSQL[T](sql: String)(op: ResultSet => T): Try[T] = {
for {
statement <- Try { connection.prepareStatement(sql) }
result <- managed(statement).map(execStatement(op)).asTry.flatten
} yield result
}
</snip>

(#asTry converts scala-arm's ExtractableManagedResource to a Try through
#either, #ifNullFail(v) yields Success(v) if v != null, an NPE, otherwise.)

Can you help me do better? :)

Thanks and best regards,
Patrick

lutzh

unread,
Apr 3, 2015, 7:56:13 AM4/3/15
to scala...@googlegroups.com
Hi,

I expect the answers you'll get to this question will reflect different opinions on programming style. Some will say you should never throw exceptions, as that breaks referential transparency, others will say throwing exceptions is ok. 

The "Functional Programming in Scala" - http://www.manning.com/bjarnason/ - book is clearly against using exceptions:

"As we just discussed, exceptions break Referential Transparency and introduce context dependence, moving us away from the simple reasoning of the substitution model and making it possible to write confusing exception-based code. This is the source of the folklore advice that exceptions should be used only for “error handling,” not for “control flow.” 

I personally think the focus on referential transparency is not too helpful when discussing exceptions. You could argue that it actually is referentially transparent, as exception throwing equals evaluation to the bottom type, which is a legal result. Indeed a "throw" expression in Scala will be inferred to be of type "Nothing".

And in practice, on the JVM, exceptions are a "fact of life", they'll always be there. They indicate abnormal termination, and if my program terminates abnormally, referential transparency might not my main concern. Also, if Try is considered the functional alternative, that will not contain for example an OutOfMemoryError, so you'll still have to reason about the exception case.

Lastly, using Try as the return type forces the caller to explicitly deal with the exception case. "https://tersesystems.com/2012/12/27/error-handling-in-scala/" makes a good point that this is much like re-introducing checked exceptions.

So my style advice, and as it is about style, it'll always be a matter of opinion, is:

- Do throw exceptions for abnormal termination
- Do use Try to catch exceptions, on the caller side, for nice chaining
- Avoid using Try as the return type

I'm looking forward to reading the other opinions. I hope the posters won't be too harsh on me..

Andrea

unread,
Apr 3, 2015, 8:27:09 AM4/3/15
to scala...@googlegroups.com
My 2 cents:

cent number 1:
I am not sure if referential transparency is important in case of abnormal termination, as you said. By abnormal termination I mean what in Java would be an unchecked exception. To me an exception is okay in such a case

cent number 2:
for exceptions that belong to the control flow, the checked exceptions, I would use Option or Either. The problem is that the code becomes less readable. I was looking for a solution to that myself

Oliver Ruebenacker

unread,
Apr 3, 2015, 8:38:21 AM4/3/15
to lutzh, scala-user

     Hello,

  Exceptions should not be handled uniformly, but depending on whether you can recover from it, and who is responsible for the recovery.

  Errors and RuntimeExceptions are considered unrecoverable. Errors, because it is unsafe to proceed (e.g. an OutOfMemoryError is likely to be followed by even more OutOfMemoryErrors) and RuntimeExceptions are considered the result of bugs. For example, a NullPointerException is considered a bug, because if you knew something could be null, you should have checked it and provided an alternative for the case of null. Exceptions are expensive to create (they need to know the entire stack trace) and most of the time you can not be entire sure where they originated (e.g. which reference was null).

  All other Exceptions (i.e. Exceptions that are not RuntimeExceptions) are considered potentially recoverable, but it depends on the context. For example, if you have a FileNotFoundException, it depends on who provided the file path. If the caller provided a file path, the caller needs to be somehow notified that the file was not there. You may well use a custom class for that, e.g.

trait Result
case class Success(data: MyData) extends Result
case class FileNotFound(file: File) extends Result

  In other cases, if a file is not where it is expected, this may indicate a bug and then it may not be recoverable and you may choose to let the Exception percolate to the top where it shuts down the app with an error message.

     Best, Oliver 


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



--
Oliver Ruebenacker
Solutions Architect at Altisource Labs
Be always grateful, but never satisfied.

Jon Pretty

unread,
Apr 3, 2015, 9:03:31 AM4/3/15
to Andrea, scala...@googlegroups.com
Hi Patrick,

Everything lutzh, Andrea and Oliver have said is good advice. But the problem remains that different people will always favor different approaches to how to handle exceptions, and library designers are usually forced into making a choice about how they handle exceptions.

So, I developed the concept of "modes" to work around this problem, so libraries can abstract over the exception handling mechanism (and return type), and I've used this extensively in the various Rapture projects. You control the return type by importing an implicit, so for example:

import rapture.core.modes.returnTry._
Json.parse("{}")
// returns `Success(json"{}")`

import rapture.core.modes.returnOption._
Json.parse("{}")
// now returns `Some(json"{}")`

import rapture.core.modes.throwExceptions._
Json.parse("{}")
// now just returns `json"{}"`

Using it requires a few superficial changes to the method implementation, but that's all explained on the Github page, here:

   https://github.com/propensive/rapture-core/#modes

Hope that's useful, or at least interesting!

Cheers,
Jon

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.

Patrick Roemer

unread,
Apr 3, 2015, 10:06:58 AM4/3/15
to scala...@googlegroups.com
Responding to lutzh:

Thanks a lot for the detailed and insightful answer!

> I expect the answers you'll get to this question will reflect different
> opinions on programming style. Some will say you should never throw
> exceptions, as that breaks referential transparency, others will say
> throwing exceptions is ok.

I'm aware that there is not - cannot be, I guess ;) - a universally
accepted style. I'm looking for - ideally consistent - approaches that
are based on some experience "from the trenches" and work well for some
team/project that I can use as a basis to derive my own.

> The "Functional Programming in Scala" - http://www.manning.com/bjarnason/ -
> book is clearly against using exceptions:
>
> "As we just discussed, exceptions break Referential Transparency and
> introduce context dependence, moving us away from the simple reasoning of
> the substitution model and making it possible to write confusing
> exception-based code. This is the source of the folklore advice that
> exceptions should be used only for “error handling,” not for “control
> flow.”
>
> I personally think the focus on referential transparency is not too helpful
> when discussing exceptions. You could argue that it actually is
> referentially transparent, as exception throwing equals evaluation to the
> bottom type, which is a legal result. Indeed a "throw" expression in Scala
> will be inferred to be of type "Nothing".

Ok, fair enough.

> And in practice, on the JVM, exceptions are a "fact of life", they'll
> always be there. They indicate abnormal termination, and if my program
> terminates abnormally, referential transparency might not my main concern.
> Also, if Try is considered the functional alternative, that will not
> contain for example an OutOfMemoryError, so you'll still have to reason
> about the exception case.

Good point. But one could probably see those (abnormal program
termination, OOME) as only covering the Java unchecked exception space,
leaving the discussion still open for checked/expected/recoverable
exceptions...?

> Lastly, using Try as the return type forces the caller to explicitly deal
> with the exception case.
> "https://tersesystems.com/2012/12/27/error-handling-in-scala/" makes a good
> point that this is much like re-introducing checked exceptions.

That's a discussion where I'm not sure where I stand, yet. Exceptions
feel like a part of the method contract to me, and in a statically typed
language it seems natural to encode them in the type system.

There's the databricks style guide that recommends using (unchecked)
exceptions and document thoroughly:

https://github.com/databricks/scala-style-guide#exception

That feels a bit strange to me, as it comes with the usual downsides of
comments: They're not compiler-checked, they'll get out of sync with the
actual code sooner or later, and, most notably: Nobody will read them.
;) So in general I think there's something to be said for encoding
exceptions as part of the method contract within the language/type
system rather than through documentation conventions. But, coming from
Java, of course I'm aware of the downsides of checked exceptions as they
exist there.

Now I'm wondering if exceptional values encoded within the type system
(Try, Either) impose the same problems as classical checked exceptions.
There's the "criticism of checked exceptions" section in the "Exceptions
debate" linked from the Terse Systems blog post:

http://www.ibm.com/developerworks/java/library/j-jtp05254/index.html

Most of the points listed there ("expose implementation details",
"unstable signatures", "swallowing") don't seem to apply (fully) to the
Try/Either approach: Try doesn't encode the concrete exception type, nor
did most usages of Either I've seen, and swallowing is much harder with
those, too. This only leaves the "unreadable code" and "too much
wrapping" points, where "unreadable" is in the eye of the beholder, and
the "wrapping" turns to functional/monadic combinator usage.

> So my style advice, and as it is about style, it'll always be a matter of
> opinion, is:
>
> - Do throw exceptions for abnormal termination
> - Do use Try to catch exceptions, on the caller side, for nice chaining
> - Avoid using Try as the return type
> - Read "https://tersesystems.com/2012/12/27/error-handling-in-scala/"" and
> follow the advice there.

Just to make sure I understand correctly: The 2nd and 3rd would imply
that in order to "intercept" an exception (e.g. to trigger a side effect
like a rollback, or to convert to some different exception), you'd use a
Try block and then re-throw an exception from there?

How does the "Use Either to report expected failure" advice from the
blog post blend in? And how would you integrate APIs like scala-arm into
this concept?

Thanks again for the link - will have to meditate over this a bit more.

Best regards,
Patrick


Patrick Roemer

unread,
Apr 3, 2015, 12:50:24 PM4/3/15
to scala...@googlegroups.com
Responding to Andrea:
> cent number 1:
> I am not sure if referential transparency is important in case of abnormal
> termination, as you said. By abnormal termination I mean what in Java would
> be an unchecked exception. To me an exception is okay in such a case

Same for me. I was more looking for approaches to checked/expected
exception propagation.

> cent number 2:
> for exceptions that belong to the control flow, the checked exceptions, I
> would use Option or Either. The problem is that the code becomes less
> readable. I was looking for a solution to that myself

Yes, the readability question scares me a bit, too. That's why I went
for the "extremist Try" example, to get a feeling for how this looks like.

>>> <snip>
>>> private def execStatement[T](op: ResultSet => T)(statement:
>>> PreparedStatement): Try[T] = {
>>> for {
>>> _ <- Try { statement.execute() }
>>> resultSet <- ifNullFail(statement.getResultSet(), "result set")
>>> result <- managed(resultSet).map(op).asTry
>>> } yield result
>>> }
>>>
>>> def execSQL[T](sql: String)(op: ResultSet => T): Try[T] = {
>>> for {
>>> statement <- Try { connection.prepareStatement(sql) }
>>> result <- managed(statement).map(execStatement(op)).asTry.flatten
>>> } yield result
>>> }
>>> </snip>

Now this certainly looks strange to me - but then, try/catch/finally
blocks will looks strange to someone coming from a language without
exception support. I'm not sure whether this is unreadable, or just
unfamiliar. :) At the moment, to me it looks like the exception handling
aspect trumps/overarches the normal code flow. But then,
try/catch/finally kind of does so, too - I'm just used to the visual
pattern matching to mask it out...

Best regards,
Patrick



Patrick Roemer

unread,
Apr 3, 2015, 1:19:40 PM4/3/15
to scala...@googlegroups.com
Responding to Oliver Ruebenacker:

Thanks for the detailed reply!

> Exceptions should not be handled uniformly, but depending on whether you
> can recover from it, and who is responsible for the recovery.
>
> Errors and RuntimeExceptions are considered unrecoverable. Errors,
> because it is unsafe to proceed (e.g. an OutOfMemoryError is likely to be
> followed by even more OutOfMemoryErrors) and RuntimeExceptions are
> considered the result of bugs. For example, a NullPointerException is
> considered a bug, because if you knew something could be null, you should
> have checked it and provided an alternative for the case of null.
> Exceptions are expensive to create (they need to know the entire stack
> trace) and most of the time you can not be entire sure where they
> originated (e.g. which reference was null).
>
> All other Exceptions (i.e. Exceptions that are not RuntimeExceptions) are
> considered potentially recoverable, but it depends on the context. For
> example, if you have a FileNotFoundException, it depends on who provided
> the file path. If the caller provided a file path, the caller needs to be
> somehow notified that the file was not there. You may well use a custom
> class for that, e.g.
>
> trait Result
> case class Success(data: MyData) extends Result
> case class FileNotFound(file: File) extends Result
>
> In other cases, if a file is not where it is expected, this may indicate
> a bug and then it may not be recoverable and you may choose to let the
> Exception percolate to the top where it shuts down the app with an error
> message.

This seems to be pretty in line with the summary of the blog post Lutz
referred to: For "unrecoverable" exceptions, just use plain old Java
exception propagation, for "recoverable"/"expected" exceptions, use
Either (or, where appropriate, a custom data type). As a use case for
Try, this basically just leaves intermediate interception of
"unrecoverable" exceptions. Does this capture the gist of it correctly?

This sounds good to me, but still leaves a few questions for the
practical application of these guidelines.

In the JDBC example, the distinction seems a bit blurry. While some
SQLExceptions might indeed indicate a bug (bad SQL), others may just
indicate a problem that cannot be controlled from the application (DB
server down, connection lost) and needs to be "recoverable", though it's
not really "expected", and even others may be "expected"/"recoverable"
from the application's point of view (table doesn't exist). And they're
checked exceptions from their Java origins, so whoever came up with them
seems to consider them recoverable in essence. Is that a proper case for
Either/custom union type, then?

I'm also still wondering whether the structure of my example makes sense
(independent of the JDBC domain). Assuming "expected" exceptions and
putting in Either for Try, would this approach of squeezing the code
flow into the flatMap corset be considered ok?

...and finally, I'm still not sure how scala-arm (and other 3rd party
libs) blend into the picture.

Patrick Roemer

unread,
Apr 3, 2015, 1:27:17 PM4/3/15
to scala...@googlegroups.com
Responding to Jon Pretty:
> Everything lutzh, Andrea and Oliver have said is good advice. But the
> problem remains that different people will always favor different
> approaches to how to handle exceptions, and library designers are usually
> forced into making a choice about how they handle exceptions.
>
> So, I developed the concept of "modes" to work around this problem, so
> libraries can abstract over the exception handling mechanism (and return
> type), and I've used this extensively in the various Rapture projects. You
> control the return type by importing an implicit, so for example:
>
> import rapture.core.modes.returnTry._
> Json.parse("{}")
> // returns `Success(json"{}")`
>
> import rapture.core.modes.returnOption._
> Json.parse("{}")
> // now returns `Some(json"{}")`
>
> import rapture.core.modes.throwExceptions._
> Json.parse("{}")
> // now just returns `json"{}"`
>
> Using it requires a few superficial changes to the method implementation,
> but that's all explained on the Github page, here:
>
> https://github.com/propensive/rapture-core/#modes
>
> Hope that's useful, or at least interesting!

That's definitely interesting, thanks a lot for the pointer!

However, it probably won't immediately help me where I'm coming from:
I'm rather on the application side than on the library side. It'd be
great if the 3rd party libs I'm using would offer these modes, of
course, but I'm not sure incorporating this concept into my layer helps,
since there are no callers/clients on top of it who would benefit
from this flexibility, and I'm just struggling to find a more or less
unified approach for exception handling to use throughout the application.

Jon Pretty

unread,
Apr 3, 2015, 3:45:49 PM4/3/15
to Patrick Roemer, scala...@googlegroups.com
Hi Patrick,

Thanks for the reply. Yes, you're right - there's not much reason to if you don't have applications using the same API in different ways, and you don't anticipate starting with one return-type (say, in a prototype) and later moving to another.

I tend to be quite zealous with telling people about modes! I'd be really keen for other libraries to use them so everyone can benefit. (One use case I am aware of is Spray, where I believe Mathias has integrated something similar, though I've not tried it out yet...)

Cheers,
Jon

Patrick Roemer

unread,
Apr 3, 2015, 3:52:05 PM4/3/15
to scala...@googlegroups.com
Responding to Jon Pretty:
> Yes, you're right - there's not much reason to if you
> don't have applications using the same API in different ways, and you don't
> anticipate starting with one return-type (say, in a prototype) and later
> moving to another.

The latter is a good point. Of course I anticipate to do it right from
the start - but then, usually I don't. ;) So building modes at least
into package/module API boundaries might be a good precaution for
changing my mind later on.

Nick Evans

unread,
Apr 3, 2015, 4:30:06 PM4/3/15
to scala...@googlegroups.com, quellkris...@gmail.com
Here is a use case where I think exceptions work better than Try/Either:

Imagine you have some complex computation with lots of nested function calls.  You know that certain (rare) situations may arise during this computation that require the computation to be terminated and control returned to a method much higher in the call stack (essentially a different layer of the application).  In such a case it seems to me that exceptions are much more suited than burdening the return types of all the nested functions with Either/Try, especially if you know anyway that if one these situations occurs the error will just be returned right up the call stack.  In these cases I believe it is worthwhile sacrificing type safety for readability and simplicity of the code.

On the other hand, if the exceptional situation needs to be handled more locally, then encoding the possibility of the exceptional situation in the type system (by using Either/Try or some other method) is probably a good idea.


Jon Pretty

unread,
Apr 3, 2015, 4:37:28 PM4/3/15
to Nick Evans, scala...@googlegroups.com, quellkris...@gmail.com
Hi Nick,

Yes, I'd agree with that!

Maybe a more obvious use case for throwing exceptions is when working in the REPL. It's a pain to have to unwrap `Option`s, `Try`s, `Either`s etc, and it's usually trivial to change something and re-run the previous line if it throws an exception.

Cheers,
Jon

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

Vincent Marquez

unread,
Apr 3, 2015, 8:29:49 PM4/3/15
to Nick Evans, scala-user, quellkris...@gmail.com
I think this is a good example of a place where the type system could catch a bug but it won't because of unchecked exceptions.  I don't see any burden of having an accurate type system.  If your function can throw an Exception (ignoring catastrophic failures that Oliver was apt to point out), why make it an implicit return value rather than being explicit about it? 

Rex Kerr

unread,
Apr 3, 2015, 9:35:18 PM4/3/15
to Vincent Marquez, Nick Evans, scala-user, quellkris...@gmail.com
You might not want specify an explicit exceptional return value because it pollutes _everything_ you're doing with distracting clutter that does not help you actually succeed in the success case (which is often the most complicated), especially when the failure cases are quite rare and not worth handling elegantly (but they can't just be dropped on the floor).

If the exceptions are still somewhat non-rare, checked exceptions are just the ticket, actually.  In Scala, it's best done by passing in the ability to throw an exception as an implicit parameter.  When you need blocks that compose, you should have your return types handle errors.  Otherwise, this pattern works quite well:

def withException(ex: ExeceptionThrower => A): Try[A] = ???
def myMethod(foo: Foo)(implicit ex: ExceptionThrower): Bar = ???

withException{ implicit ex =>
  myMethod(foo) + myOtherMethod(foo2) + ...
}

If the exceptions are really weird and fatal stuff, then just regular unchecked exceptions are fine, with a Try or try/catch or something here and there to try to at least provide a little context for why things went wrong before dying.

  --Rex

Vincent Marquez

unread,
Apr 3, 2015, 10:12:17 PM4/3/15
to Rex Kerr, Nick Evans, scala-user, quellkris...@gmail.com
On Fri, Apr 3, 2015 at 6:35 PM, Rex Kerr <ich...@gmail.com> wrote:
You might not want specify an explicit exceptional return value because it pollutes _everything_ you're doing with distracting clutter

You could could make the same argument about types but that would just sound silly.   You can choose to ignore the fact that your function has multiple return values, or you can be explicit about it.  Nothing is forcing you to propagate the \/ all the way up the call stack, you can handle it at any time just like you would catch an exception.  

I fail to see how this causes *any* problems, where as what you are suggesting will most likely result in bugs.

--Vincent

Patrick Roemer

unread,
Apr 4, 2015, 2:06:33 PM4/4/15
to scala...@googlegroups.com
Responding to Nick Evans:
> Here is a use case where I think exceptions work better than Try/Either:
>
> Imagine you have some complex computation with lots of nested function
> calls. You know that certain (rare) situations may arise during this
> computation that require the computation to be terminated and control
> returned to a method much higher in the call stack (essentially a different
> layer of the application). In such a case it seems to me that exceptions
> are much more suited than burdening the return types of all the nested
> functions with Either/Try, especially if you know anyway that if one these
> situations occurs the error will just be returned right up the call stack.
> In these cases I believe it is worthwhile sacrificing type safety for
> readability and simplicity of the code.

Hmm, ok. But is this scenario

- a deliberate, contained breach of the rules, like performance tweaks:
Yeah, we should always use {Try, Either}, but here we have exceptional
circumstances and will use exceptions, but we'll make sure this approach
doesn't leak to the outside.

- an instance of the "mixed" approach: allow exception propagation
within module scope, but convert to explicit type at module boundary.

- the standard: use (unchecked) exceptions

> On the other hand, if the exceptional situation needs to be handled more
> locally, then encoding the possibility of the exceptional situation in the
> type system (by using Either/Try or some other method) is probably a good
> idea.

If Either/Try don't scale to global exception handling, why should I
bother using them at lower level? I'd have thought that the problem with
exceptions is that they're convenient to propagate, but brittle in the
bigger picture, which is why I came up with the idea of containing them
within some scope. If exceptions are ok in the large, why Try/Either at
more local scope at all?

Rex Kerr

unread,
Apr 4, 2015, 9:36:48 PM4/4/15
to Vincent Marquez, Nick Evans, scala-user, quellkris...@gmail.com
On Fri, Apr 3, 2015 at 7:12 PM, Vincent Marquez <vincent...@gmail.com> wrote:


On Fri, Apr 3, 2015 at 6:35 PM, Rex Kerr <ich...@gmail.com> wrote:
You might not want specify an explicit exceptional return value because it pollutes _everything_ you're doing with distracting clutter

You could could make the same argument about types but that would just sound silly.

Yes it would sound silly.  Trying that as a straw-man argument sounds silly too.  The reason you're returning things is generally because you want the value.
 
  You can choose to ignore the fact that your function has multiple return values, or you can be explicit about it.  Nothing is forcing you to propagate the \/ all the way up the call stack, you can handle it at any time just like you would catch an exception.

That's the entire problem, though.  Often the source of the problem is very much deeper than the place where you want to handle it, creating a massive propagation problem.
 

I fail to see how this causes *any* problems, where as what you are suggesting will most likely result in bugs.

If you only ever write trivial code, it doesn't cause any problems.  If you write non-trivial code, everything that distracts from the main point is, well, a distraction.  It makes it harder to solve the problem.  By removing exception handling as an ever-present concern, you can focus more fully on code correctness.

Also, since the exception is in the function signature with what I recommended, you have to be rather careless to have any problems.  (Not saying you can't be that careless--you can pass it off to another thread, you can store the exception-generator, etc., but similarly disastrous behavior can happen with many constructs where e.g. thread-locality is assumed.)

  --Rex

Rex Kerr

unread,
Apr 4, 2015, 9:39:08 PM4/4/15
to Patrick Roemer, scala-user
I tend to agree with you here.  Exceptions locally, Try/Either globally.

At some point you will probably end up writing your own OhWowThisIsBorked class to encapsulate all the varied top-level problems, but that's better than having an unruly mess of non-specific exceptions that may pop up at any time.

  --Rex
 

Oliver Ruebenacker

unread,
Apr 4, 2015, 10:30:36 PM4/4/15
to Rex Kerr, Patrick Roemer, scala-user

     Hello,

  It would be great to have a compiler that allows something that combines the best of exceptions and complex monads like Try/Either. Something that works like monads, but does not clutter up the syntax.

  Something along the lines of this:

  There is a type "A throws B", which implies that the reference has type A in success mode and type B in failure mode.

  There can be multiple failure modes represented by types like "A throws B, C", which is equivalent to "A throws C, B"

  Any expression that takes a type A and returns a type D can instead take a type "A throws B", and then the return type will automatically be "D throws B". In success mode, it evaluates type A normally and returns type D, and in failure mode, it just passes through type B.

  A catch expression is a special expression that can take an argument with failure mode and not propagate the failure mode.

  For example:

  object DivisionByZero
  val random = new Random
  val a: Int = random.nextInt(10)
  val b: Int = random.nextInt(10)
  val c: Int throws DivisionByZero =
    if(b == 0) {
      throw DivisionByZero
    } else {
      a / b
    }
  val d : Int throws DivisionByZero = c + 10
  val message1: String throws DivisionByZero = "Result is " + d
  val message2: String = {
    "Result is " + d
  } catch {
    case DivisionByZero => "No result, since there was a division by zero."
  }

     Best, Oliver

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



--

Bardur Arantsson

unread,
Apr 5, 2015, 4:17:43 AM4/5/15
to scala...@googlegroups.com
On 05-04-2015 03:36, Rex Kerr wrote:
> On Fri, Apr 3, 2015 at 7:12 PM, Vincent Marquez <vincent...@gmail.com>
> wrote:
>
>> You can choose to ignore the fact that your function has multiple return
>> values, or you can be explicit about it. Nothing is forcing you to
>> propagate the \/ all the way up the call stack, you can handle it at any
>> time just like you would catch an exception.
>>
>
> That's the entire problem, though. Often the source of the problem is very
> much deeper than the place where you want to handle it, creating a massive
> propagation problem.
>
>

Yes, as opposed to a massive readability/maintainability problem due to
non-local effects and/or complicated control flow. :)

This very situation is where exceptions are so incredibly seductive
("I'll just tweak this *little thing*..."), but personally I'm not sure
they're worth it. Depending on your use case, an explicit
exception/continuation monad may be better for overall readability.
YMMV, of course :).

Regards,


Rex Kerr

unread,
Apr 5, 2015, 3:13:50 PM4/5/15
to Bardur Arantsson, scala-user
On Sun, Apr 5, 2015 at 1:17 AM, Bardur Arantsson <sp...@scientician.net> wrote:
On 05-04-2015 03:36, Rex Kerr wrote:
> On Fri, Apr 3, 2015 at 7:12 PM, Vincent Marquez <vincent...@gmail.com>
> wrote:
>
>>   You can choose to ignore the fact that your function has multiple return
>> values, or you can be explicit about it.  Nothing is forcing you to
>> propagate the \/ all the way up the call stack, you can handle it at any
>> time just like you would catch an exception.
>>
>
> That's the entire problem, though.  Often the source of the problem is very
> much deeper than the place where you want to handle it, creating a massive
> propagation problem.
>
>

Yes, as opposed to a massive readability/maintainability problem due to
non-local effects and/or complicated control flow. :)

The locality of effects is perfectly documented in implicit parameters, and the control flow is _simpler_ because you don't have to work through the logic of who-knows-how-many alternate branches.  (Was there a fold in there somewhere?)

Recall that I'm not arguing for just-plain-exceptions.  I'm arguing for using implicits to enable the throwing of a particular exception that you know you want to handle.

def foo: Either[Wrong, A] = {
  // lots of internal logic changes to reflect that it's an Either
}

vs.

def foo(implicit wrong: Wrong): A = {
  // call wrong(params) when there's a problem, otherwise logic is simple
}

And from the calling side:

val maybeFoo = foo

vs.

val maybeFoo = canBeWrong{ implicit wrong => foo }

at which point the types are identical again.

If you have only simple operations, the Either way is superior (less boilerplate).  But nest three, four, five deep?  Have many complicated conditions to pass through before everything checks out okay (with intermediate computations on the way)?  Then Either gives you way more boilerplate, and less clarity about where the failure-case is actually going to be handled in some meaningful way (rather than just being passed along).

(I'm using Either to stand in for any sum type usable for error handling; depending on the one you pick your boilerplate may be less, but it's always there.)

 --Rex


Nick Evans

unread,
Apr 6, 2015, 5:56:29 AM4/6/15
to scala...@googlegroups.com, quellkris...@gmail.com
Hi Patrick,

I think I would indeed be an advocate of the "mixed" approach: exception propagation within module scope with explicit types at the boundaries, so that there are no surprises for the user of the module.

When you say "If Either/Try don't scale to global exception handling", what exactly do you mean by "global exception handling"?  I think Try/Either can work very well at module boundaries (if this is what you mean by "global").  Perhaps my usage of the word "local" was a bit confusing.  What I meant to say was that Try/Either can be unhandy if you know that you want exceptions to be propagated and handled in a quite different place from where they occurred (as you say: exceptions are convenient to propagate).  Getting this propagation behaviour with Try/Either means explicitly passing the "exception" up the call stack, which can very quickly lead to boilerplate code and obscure the semantics of the intermediate functions.

Nick Evans

unread,
Apr 6, 2015, 6:12:26 AM4/6/15
to scala...@googlegroups.com, ich...@gmail.com, quellkris...@gmail.com
Oliver, what is the fundamental difference between your suggestion and checked exceptions?

You write:

 "Any expression that takes a type A and returns a type D can instead take a type "A throws B", and then the return type will automatically be "D throws B". In success mode, it evaluates type A normally and returns type D, and in failure mode, it just passes through type B."

But if the compile time return type of a function is D, then the return type can never "automatically" be "D throws B", unless you mean that this is the runtime return type of the expression in which case this amounts to 
an unchecked exception and the compiler is not actually doing any "exception handling checking" for you.

Or do you mean that if a function has an input type A throws B, then the return type will also always include "throws B"?  But then what if the function handles the B exception?  

Patrick Roemer

unread,
Apr 6, 2015, 7:24:16 AM4/6/15
to scala...@googlegroups.com
Responding to Nick Evans:
> I think I would indeed be an advocate of the "mixed" approach: exception
> propagation within module scope with explicit types at the boundaries, so
> that there are no surprises for the user of the module.

...but then, how to proceed in the calling module? Convert to an
exception again for consistency of further propagation internal to this
module? Or propagate the Either/Try as is, thus effectively creating a
ragbag of exception propagation approaches within this module?

> When you say "If Either/Try don't scale to global exception handling", what
> exactly do you mean by "global exception handling"? I think Try/Either can
> work very well at module boundaries (if this is what you mean by "global").
> Perhaps my usage of the word "local" was a bit confusing. What I meant to
> say was that Try/Either can be unhandy if you know that you want exceptions
> to be propagated and handled in a quite different place from where they
> occurred (as you say: exceptions are convenient to propagate). Getting
> this propagation behaviour with Try/Either means explicitly passing the
> "exception" up the call stack, which can very quickly lead to boilerplate
> code and obscure the semantics of the intermediate functions.

But doesn't this reasoning lead to confinement of Either/Try usage to
some limited local scope (where I know that the exception will be
dissolved one or two frames higher up the stack)? "Another module" to me
almost seems to be a synonym for "a quite different place". I usually
don't know the context a module's API is used within, in particular I
don't know how high up the call stack the client code will be able to
handle any exceptional circumstances.

So this line of thought seems to lead me to a) use exceptions only or b)
use Either/Try at module boundaries, but in general use exceptions
inside modules. For the "local" case, the choice doesn't seem that
important - it's confined to a call chain of two or three
methods/functions, and it doesn't leak to public API (which, by
definition, would be a module boundary).

Oliver Ruebenacker

unread,
Apr 6, 2015, 7:53:04 AM4/6/15
to Nick Evans, scala-user, Rex Kerr, Patrick Roemer

     Hello,

  The difference between my suggestion and checked exception is that my suggestion is a monad under the hood without the syntactic clutter. And it is not restricted to Exceptions (Exceptions well-intentioned, but expensive and crude).

  Let us call it Maybe[A, B] with subtypes Yes[A] and No[B]. Maybe[A, B] replaces Either[A, B]. Maybe[A, Exception] replaces Try[A]. Maybe[A, Nothing] replaces Option[A].

  The whole idea is that the compiler avoids the clutter and optimizes as appropriate. Say, x is of type A and has a method m which returns C:

  val y = x.m

  Then, if x is of type "A throws B" (Maybe[A, B] under the hood), the compiler allows us to still write

  val y = x.m

  But now y is of type "C throws B", because under the hood, this is rewritten (de-sugared, if you like), to:

  val yMaybe : Maybe[D, B] = xMaybe.map(_.m)

  To get back to a normal (no-throws) value, you catch, so you write (with recoverFrom: B => D):

  val z : D = y catch { case b:B => recoverFrom(b) }

  This gets de-sugared to:

  val z = yMaybe match {
    case Yes(y) => y
    case No(b) => recoverFrom(b)
  }

  I always hear: monads are cool, but the syntactic clutter sucks. Why not have compiler-support for clutter-free monads?

     Best, Oliver

Vincent Marquez

unread,
Apr 6, 2015, 2:17:43 PM4/6/15
to Rex Kerr, Bardur Arantsson, scala-user

If you have only simple operations, the Either way is superior (less boilerplate).  But nest three, four, five deep? 



Why does it matter?  Your claim that Either clutter's your codebase is jsust wrong. You can even *ignore* that a method returns an either and completely abstract over it.  

     def getUser(a:String): \/[Error, User]


     def giveUserMoney(s: String, i: Int) = 
         getUser(s).flatMap(_.addMoney(i))

I don't care if getUser returns a Future, an Option, an Either, or a monad transformer stack.  I know inside the context is a user.  

Doesn't pollute my method at all. 

--Vincent



     




 
Have many complicated conditions to pass through before everything checks out okay (with intermediate computations on the way)?  Then Either gives you way more boilerplate, and less clarity about where the failure-case is actually going to be handled in some meaningful way (rather than just being passed along).

(I'm using Either to stand in for any sum type usable for error handling; depending on the one you pick your boilerplate may be less, but it's always there.)

 --Rex


Oliver Ruebenacker

unread,
Apr 6, 2015, 2:49:24 PM4/6/15
to Vincent Marquez, Rex Kerr, Bardur Arantsson, scala-user

     Hello,

  The fact that instead of simply writing:

  def giveUserMoney(s: String, i: Int) = 
         getUser(s).addMoney(i)

  Instead you had to write:

def giveUserMoney(s: String, i: Int) = 
         getUser(s).flatMap(_.addMoney(i))

  That is clutter. It becomes worse when you have something like this:

   f(x: Double, y:Double, z:Double): Double = x*y*z/(x + y + z)

  Now, replace Double by Try[Double] and tell me that's not clutter.

     Best, Oliver
  

Vincent Marquez

unread,
Apr 6, 2015, 3:17:21 PM4/6/15
to Oliver Ruebenacker, Rex Kerr, Bardur Arantsson, scala-user
On Mon, Apr 6, 2015 at 11:49 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:

     Hello,

  The fact that instead of simply writing:

  def giveUserMoney(s: String, i: Int) = 
         getUser(s).addMoney(i)

  Instead you had to write:

def giveUserMoney(s: String, i: Int) = 
         getUser(s).flatMap(_.addMoney(i))

  That is clutter. It becomes worse when you have something like this:

   f(x: Double, y:Double, z:Double): Double = x*y*z/(x + y + z)

  Now, replace Double by Try[Double] and tell me that's not clutter.

Is it more clutter than passing an implicit parameter for each possible error scenario, then doing if checks to see if something is wrong?  

So I have to do one more line of code than yours, though I'm being much more explicit about behavior:

f(x: Try[Double], y: Try[Double], z: Try[Double]): Try[Double] = {
   val ap = x |@| y |@| z  //returns an 'applicative'
   ap( (x,y,z) => x*y*z/(z+y+z) )  // applicative builder takes a function that matches the number of parameters in the applicative'
}

You can always fold over the try at *any* time to get out of the context.  Also, in my example it's trivial to add concurrency.  If you're *already* in a Future, you have the same amount of 'clutter', but your types reveal much more and prevent bugs.

--Vincent
     

Rex Kerr

unread,
Apr 6, 2015, 3:42:53 PM4/6/15
to Vincent Marquez, Oliver Ruebenacker, Bardur Arantsson, scala-user
On Mon, Apr 6, 2015 at 12:17 PM, Vincent Marquez <vincent...@gmail.com> wrote:


On Mon, Apr 6, 2015 at 11:49 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:

     Hello,

  The fact that instead of simply writing:

  def giveUserMoney(s: String, i: Int) = 
         getUser(s).addMoney(i)

  Instead you had to write:

def giveUserMoney(s: String, i: Int) = 
         getUser(s).flatMap(_.addMoney(i))

  That is clutter. It becomes worse when you have something like this:

   f(x: Double, y:Double, z:Double): Double = x*y*z/(x + y + z)

  Now, replace Double by Try[Double] and tell me that's not clutter.

Is it more clutter than passing an implicit parameter for each possible error scenario, then doing if checks to see if something is wrong?  

So I have to do one more line of code than yours, though I'm being much more explicit about behavior:

f(x: Try[Double], y: Try[Double], z: Try[Double]): Try[Double] = {
   val ap = x |@| y |@| z  //returns an 'applicative'
   ap( (x,y,z) => x*y*z/(z+y+z) )  // applicative builder takes a function that matches the number of parameters in the applicative'
}

And you made a trivial but not-quite-so-easy-to-spot-any-more typo that breaks the logic (and your type system doesn't help you).

And even if you had typed it all in correctly, you added two new spots at which someone, e.g. me, who is "good" at doing so, could make a naming error.  Thanks, but I already named my variables.  I don't want to do it any more.

If instead you had

  trait One[A] { def value(implicit ex: CanThrow): A }
  implicit def unwrap[A](oa: One[A])(implicit ex: CanThrow) = oa.value(ex)

then you could

  f(x: One[Double], y: One[Double], z: One[Double])(implicit ex: CanThrow): Double =
    x*y*z/(x+y+z)

and your logic _is unaffected_.  You can parameterize CanThrow by as many types as you need to keep straight who is actually allowed to throw, making it equally typesafe as the infect-the-logic-with-monadic-boilerplate solution.

With Oliver's proposal also (as I understand it), you would decorate all the types with "throws" and again retain the arithmetic logic shown there (with it being syntactic sugar for something a lot more like what the applicative does).

  --Rex

Vincent Marquez

unread,
Apr 6, 2015, 4:15:47 PM4/6/15
to Rex Kerr, Oliver Ruebenacker, Bardur Arantsson, scala-user
On Mon, Apr 6, 2015 at 12:42 PM, Rex Kerr <ich...@gmail.com> wrote:
On Mon, Apr 6, 2015 at 12:17 PM, Vincent Marquez <vincent...@gmail.com> wrote:


On Mon, Apr 6, 2015 at 11:49 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:

     Hello,

  The fact that instead of simply writing:

  def giveUserMoney(s: String, i: Int) = 
         getUser(s).addMoney(i)

  Instead you had to write:

def giveUserMoney(s: String, i: Int) = 
         getUser(s).flatMap(_.addMoney(i))

  That is clutter. It becomes worse when you have something like this:

   f(x: Double, y:Double, z:Double): Double = x*y*z/(x + y + z)

  Now, replace Double by Try[Double] and tell me that's not clutter.

Is it more clutter than passing an implicit parameter for each possible error scenario, then doing if checks to see if something is wrong?  

So I have to do one more line of code than yours, though I'm being much more explicit about behavior:

f(x: Try[Double], y: Try[Double], z: Try[Double]): Try[Double] = {
   val ap = x |@| y |@| z  //returns an 'applicative'
   ap( (x,y,z) => x*y*z/(z+y+z) )  // applicative builder takes a function that matches the number of parameters in the applicative'
}

And you made a trivial but not-quite-so-easy-to-spot-any-more typo that breaks the logic (and your type system doesn't help you).

And even if you had typed it all in correctly, you added two new spots at which someone, e.g. me, who is "good" at doing so, could make a naming error.  Thanks, but I already named my variables.  I don't want to do it any more.


Pointing out a typo doesn't help your argument, unless you're going to claim if I didn't make a typo that it would be valid? 

We don't need to rename things, we could easily do it on one line

 
If instead you had

  trait One[A] { def value(implicit ex: CanThrow): A }
  implicit def unwrap[A](oa: One[A])(implicit ex: CanThrow) = oa.value(ex)

then you could

  f(x: One[Double], y: One[Double], z: One[Double])(implicit ex: CanThrow): Double =
    x*y*z/(x+y+z)

and your logic _is unaffected_.  You can parameterize CanThrow by as many types as you need to keep straight who is actually allowed to throw, making it equally typesafe as the infect-the-logic-with-monadic-boilerplate solution.

My logic was also unaffected.   How can I tell if y has failed?  What if I want to try and re-get y? Is that easy to do?  What if I have a list of values that may or may not succeed? 

We're reinventing the wheel here, and I don't think we're doing a good job of it.  

What if we have Future values?  How do you propose we handle 

f(x: Future[Double], y: Future[Double], z: Future[Double]) 

with the possibility that each of them may have failed or succeeded?  

--Vincent

Andrea

unread,
Apr 7, 2015, 5:16:09 AM4/7/15
to scala...@googlegroups.com
Jeez Patrick, I see you started a lively thread here. in 3 days there has been quite a bit of discussion

If you have to write a try/catch at every line then obviously it loses every time against Option/Either (sorry, I'm not looking at Try here as it implies some sort of exception to me; I would use it only to translate an exception into an either). But this is the advantage of the try/catch: you don't have to write it at every line. In that sense I would say try/catch has probably an advantage.
However, there is a catch here (pardon the pun): referential transparency. The general opinion is that exceptions break referential transparency. Now, I'll be honest: I'm starting to question that, and I think this will be for me food for thought. But in the moment that you want to keep your referential transparency I don't think there is a choice: what is considered an expected error (a checked exception) should not be an exception.
You can obviously choose a less puristic approach and say "I'm willing to give up referential transparency in order to have more readability".

I don't think there is really a silver bullet. Every solution I have seen goes in one of these directions. Personally I don't consider the increased complexity of code deriving from the use of Either a particular problem, so I would go that way, but I understand if people choose a different approach

Rex Kerr

unread,
Apr 7, 2015, 2:09:59 PM4/7/15
to Vincent Marquez, Oliver Ruebenacker, Bardur Arantsson, scala-user
On Mon, Apr 6, 2015 at 1:15 PM, Vincent Marquez <vincent...@gmail.com> wrote:


On Mon, Apr 6, 2015 at 12:42 PM, Rex Kerr <ich...@gmail.com> wrote:
On Mon, Apr 6, 2015 at 12:17 PM, Vincent Marquez <vincent...@gmail.com> wrote:


On Mon, Apr 6, 2015 at 11:49 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:

     Hello,

  The fact that instead of simply writing:

  def giveUserMoney(s: String, i: Int) = 
         getUser(s).addMoney(i)

  Instead you had to write:

def giveUserMoney(s: String, i: Int) = 
         getUser(s).flatMap(_.addMoney(i))

  That is clutter. It becomes worse when you have something like this:

   f(x: Double, y:Double, z:Double): Double = x*y*z/(x + y + z)

  Now, replace Double by Try[Double] and tell me that's not clutter.

Is it more clutter than passing an implicit parameter for each possible error scenario, then doing if checks to see if something is wrong?  

So I have to do one more line of code than yours, though I'm being much more explicit about behavior:

f(x: Try[Double], y: Try[Double], z: Try[Double]): Try[Double] = {
   val ap = x |@| y |@| z  //returns an 'applicative'
   ap( (x,y,z) => x*y*z/(z+y+z) )  // applicative builder takes a function that matches the number of parameters in the applicative'
}

And you made a trivial but not-quite-so-easy-to-spot-any-more typo that breaks the logic (and your type system doesn't help you).

And even if you had typed it all in correctly, you added two new spots at which someone, e.g. me, who is "good" at doing so, could make a naming error.  Thanks, but I already named my variables.  I don't want to do it any more.


Pointing out a typo doesn't help your argument, unless you're going to claim if I didn't make a typo that it would be valid? 

We don't need to rename things, we could easily do it on one line

Can you show the example with no renaming?  It's not about number of lines, it's about introducing more variable names (in this case, which shadow the originals).

In particular, instead of making the compiler keep your argument order straight, you are taking on that burden manually.  If you can use dummy variables like x, y, z, it's pretty easy.  If you have more realistic variable names like "firstName", "lastName", "address", "height", "width", "depth", with no universally-acknowledged predefined order, it's a significant source of bugs.  At least it is for me.  Is it width, depth, height?  Or height, width, depth?  The more times I have to keep it straight, the less able I am to just list them and the more likely it is that I'll have to create a case class to encapsulate them.  Not that this is very hard, but it's extra work, and all these little things add up.


 
If instead you had

  trait One[A] { def value(implicit ex: CanThrow): A }
  implicit def unwrap[A](oa: One[A])(implicit ex: CanThrow) = oa.value(ex)

then you could

  f(x: One[Double], y: One[Double], z: One[Double])(implicit ex: CanThrow): Double =
    x*y*z/(x+y+z)

and your logic _is unaffected_.  You can parameterize CanThrow by as many types as you need to keep straight who is actually allowed to throw, making it equally typesafe as the infect-the-logic-with-monadic-boilerplate solution.

My logic was also unaffected.   How can I tell if y has failed?

It's unaffected (if you get your variable order straight), but it's cluttered.

You can tell if y has failed because it throws an exception.  If you care that it's y instead of x or z, and the failure of y has no specific information that lets you identify it, then you are outside of the use case of this construct.  Use something else!

 
What if I want to try and re-get y? Is that easy to do?

No, it's not.  But you're trying to do something else.  Implicit-gated exceptions are not intended to be a universal solution.  They are, rather, a (in some cases much) superior solution to a certain class of problems.
 
What if I have a list of values that may or may not succeed? 

You again want something else.
 

We're reinventing the wheel here, and I don't think we're doing a good job of it.

I never proposed to _not_ use a monadic approach in places where you _do_ care about all the exceptional cases.  I'm proposing this specifically for when you have rare things that go wrong and you don't care very much about them except that you at least don't fall on your face but instead handle them in some well-defined location.

The monadic approach violates separation of concerns very badly if you actually want your concerns separated.  You have little (or big!) traces of "I had to care that this might have gone wrong" all throughout your code.  But if you don't _want_ your concerns separated because you have various different strategies you want to employ locally in the case of failure in various spots, then monadic error handling is awesome because it won't let you forget to handle your error cases, and it gives you tools (with a good library) for facilitating the various strategies.
 

What if we have Future values?  How do you propose we handle 

f(x: Future[Double], y: Future[Double], z: Future[Double]) 

with the possibility that each of them may have failed or succeeded?  

If you are happy to block until each is done, and the whole routine is toast of any one of them fails, you can write an exception-throwing implict-gated getter and it's just like the previous solution.  No different than monads needing their own flatMap.

If you want to do something involved with the failure cases, or be blocking-aware, or want to produce a Future of the computation instead of handle it eagerly, then you use standard approaches instead.

Let's give an example of parsing.  Suppose we have a text string with two Doubles, a Boolean, an Int, and finally a String.  If you have implicit-gated exceptions, the parser can look something like this:

  case class Line(x1: Double, x2: Double, b: Boolean, n: Int, name: String)

  def parse(p: TokenStream)(implicit ex: CanThrow[p.type]): Line =
    Line(p.double, p.double, p.boolean, p.int, p.string)

The token stream is free to throw information about what it was trying to parse and where, so you know quite a bit about what went wrong if it fails.

I've taken an really easy task--parsing a line of text with a static set of tokens--and kept it really easy.  You just do exactly what you want.  And I magically link into error-handling code by simply including one implicit.  (Given that the TokenStream is built to use this scheme.)  If I wanted to try to parse every token and return every error, it wouldn't work, but _I don't want to_.

The simplest corresponding explicit sum-type-based solution (monadic or otherwise) is not nearly so simple.  (Feel free to try.)

  --Rex

Sonnenschein

unread,
Apr 9, 2015, 12:14:07 PM4/9/15
to scala...@googlegroups.com, vincent...@gmail.com, cur...@gmail.com, sp...@scientician.net
Rex, your example built on trait One seems appealing but is not working out of the box: How would you call f and how would * be a member of One? Could you provide a fully working example?
Thanks, Peter

Rex Kerr

unread,
Apr 9, 2015, 3:40:15 PM4/9/15
to Sonnenschein, scala-user, Vincent Marquez, Oliver Ruebenacker, Bardur Arantsson
Here's a complete implementation to try out in the REPL.  I don't necessarily recommend writing wrapper classes like One; normally I'd have something that gets a Double that takes an (implicit ex: CanThrow) and directly get the Double when I need it.

Note how easily you can (safely!) change what can use unsafe functionality: just add (implicit ex: CanThrow), and you get to call methods that might throw.  (See foo using f.)  But you end up not with exceptions that might leak out everywhere but everything tidily packaged in an Either.  Checked exceptions, but done better.  (Better enough that I find them the best solution for the job in a non-negligible number of cases.)

// :paste in REPL starting here
class Checked(s: String) extends Exception(s) {}

trait CanThrow {
  def fail(s: String) = throw new Checked(s)
}
object Checker {
  def apply[A](f: CanThrow => A): Either[Checked, A] = {
    val ct = new CanThrow {}
    try { Right(f(ct)) } catch { case ch: Checked => Left(ch) }

  }
}

trait One[A] { def value(implicit ex: CanThrow): A }
class OneExists[A](myValue: A) extends One[A] {
  def value(implicit ex: CanThrow): A = myValue
}
class OneDoesnt[A](msg: String) extends One[A] {
  def value(implicit ex: CanThrow): A = ex.fail(msg)
}
object One {
  def apply[A](a: A): One[A] = new OneExists[A](a)
  def not[A](s: String): One[A] = new OneDoesnt[A](s)

}

implicit def unwrap[A](oa: One[A])(implicit ex: CanThrow) = oa.value(ex)
// End :paste in REPL



type OD = One[Double]   // Just to make lines shorter

def f(x: OD, y: OD, z: OD)(implicit ex: CanThrow): Double =
    x*y*z/(x+y+z)

def foo(x: OD, y: OD)(implicit ex: CanThrow): Double = {
  if (x.value == y.value) ex.fail("same values are boring")
  f(x,y,x) - f(y,x,y) + x
}

val odx: OD = One(1.0)
val ody: OD = One(2.0)
val odz: OD = One.not[Double]("all broken")


Checker{ implicit ex =>
  println(odx + ody)
  println(foo(odx, ody))
  println(f(odx, ody, odx))
  f(odx, ody, odz)
}

// f(odx, ody, odz)   <-- does not compile, no implicit ex in scope

  --Rex


On Thu, Apr 9, 2015 at 9:14 AM, Sonnenschein <peter...@arcor.de> wrote:
Rex, your example built on trait One seems appealing but is not working out of the box: How would you call f and how would * be a member of One? Could you provide a fully working example?
Thanks, Peter

--

Patrick Roemer

unread,
Apr 10, 2015, 10:06:36 AM4/10/15
to scala...@googlegroups.com
Responding to Andrea:
> Jeez Patrick, I see you started a lively thread here. in 3 days there has
> been quite a bit of discussion

Yeah, that's what I had hoped for. :) It's been very helpful to me.

> If you have to write a try/catch at every line then obviously it loses
> every time against Option/Either (sorry, I'm not looking at Try here as it
> implies some sort of exception to me; I would use it only to translate an
> exception into an either).

That's one takeaway from this discussion: Preferring Either over Try for
expected exceptional cases definitely makes sense to me.

> I don't think there is really a silver bullet. Every solution I have seen
> goes in one of these directions. Personally I don't consider the increased
> complexity of code deriving from the use of Either a particular problem, so
> I would go that way, but I understand if people choose a different approach

Indeed. I guess I'll just play with a "strict Either" approach in my
next toy project and see how this feels.

Thanks a lot to everybody joining the discussion!

Best regards,
Patrick


Reply all
Reply to author
Forward
0 new messages