1

May be my design is flawed (most probably it is) but I have been thinking about the way Option is used in Scala and I am not so very happy about it. Let's say I have 3 methods calling one another like this:

def A(): reads a file and returns something
def B(): returns something
def C(): Side effect (writes into DB)

and C() calls B() and in turn B() calls A()

Now, as A() is dependent on I/O ops, I had to handle the exceptions and return and Option otherwise it won't compile (if A() does not return anything). As B() receives an Option from A() and it has to return something, it is bound to return another Option to C(). So, you can possibly imagine that my code is flooded with match/case Some/case None (don't have the liberty to use getOrElse() always). And, if C() is dependent on some other methods which also return Option, you would be scared to look at the definition of C().

So, am I missing something? Or how flawed is my design? How can I improve it?

Mrinal
  • 1,826
  • 2
  • 19
  • 31
  • 2
    Whether you use `Option` or do it the Java way, using `null` or something else, you will have to write code to specify what happens in case there is no result. So there's no way to completely escape handling that. Note that you don't *have* to use `match/case Some/case None`, `Option` also has methods like `map`, etc. and you can use it in a `for`. – Jesper Dec 02 '17 at 08:11

2 Answers2

3

Using match/case on type Option is often useful when you want to throw away the Option and produce some value after processing the Some(...) but a different value of the same type if you have a None. (Personally, I usually find fold to be cleaner for such situations.)

If, on the other hand, you're passing the Option along, then there are other ways to go about it.

def a():Option[DataType] = {/*read new data or fail*/}

def b(): Optioon[DataType] = {
  ... //some setup
  a().map{ inData =>
    ... //inData is real, process it for output
  }
}
def c():Unit = {
  ... //some setup
  b().foreach{ outData =>
    ... //outData is real, write it to DB
  }
}
jwvh
  • 50,871
  • 7
  • 38
  • 64
  • I think `flatMap` is also worth mentioning for the last scenario ("_And, if `C()` is dependent on some other methods which also return `Option`, you would be scared to look at the definition of `C()`_") – SergGr Dec 02 '17 at 12:11
0

am I missing something?

Option is one design decision, but there can be others. I.e what happens when you want to describe the error returned by the API? Option can only tell you two kinds of state, either I have successfully read a value, or I failed. But sometimes you really want to know why you failed. Or more so, If I return None, is it because the file isn't there or because I failed on an exception (i.e. I don't have permission to read the file?).

Whichever path you choose, you'll usually be dealing with one or more effects. Option is one such effect which representing a partial function, i.e. this operation may not yield a result. While using pattern matching with Option, as other said, is one way of handling it, there are other operations which decrease the verbosity.

For example, if you want to invoke an operation in case the value exists and another in case it isn't and they both have the same return type, you can use Option.fold:

scala> val maybeValue = Some(1)
maybeValue: Some[Int] = Some(1)

scala> maybeValue.fold(0)(x => x + 1)
res0: Int = 2

Generally, there are many such combinators defined on Option and other effects, and they might seem cumbersome at the beginning, later they come to grow on you and you see their real power when you want to compose operations one after the other.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321