4

This is probably more a question about functional programming than about Java 8 specifically, but it's what I'm using right now.

I have a source object (could represent a repository, or a session..., doesn't matter here) that has a method retrieveSomething() that returns an Optional<SomethingA>. I have a method somewhere that returns a Something, by calling retrieveSomething() and providing a default value in case the optional was empty, as follows:

return source.retrieveSomething()
             .orElseGet(() -> provideDefaultValue());

Now I want to modify this code so that in case the source didn't contain any value yet (so the optional was empty), the source is updated with the provided default value.

Of course, I could easily do that inside a lambda expression code block:

return source.retrieveSomething()
             .orElseGet(() -> {
                     Something sth = provideDefaultValue()
                     source.putSomething(sth);
                     return sth;
                 });

But if I understand correctly, I'm not supposed to use functions that cause side effects. So what's the "correct" (functional) way to do this, keeping the benefit of using Optional (in real code I'm actually also performing a map operation on it, but that's irrelevant here) ?

herman
  • 11,740
  • 5
  • 47
  • 58
  • 3
    Wait a minute. You're trying to updating the `Optional`? Conceptually, this makes no sense; the `Optional` either exists, or you provide a new value in lieu of it. – Makoto Jul 13 '15 at 17:43
  • Define "side effect" here. It is unclear what your inputs are. – fge Jul 13 '15 at 17:48
  • @Makoto no I'm not trying to update the `Optional`. I want to update the source where the `Optional` is coming from (could be DB, REST service, HTTP session, ...), so that next time when `retrieveSomething()` is called, it won't return the empty `Optional`. Let's imagine `provideDefaultValue()` is a very heavy computation. – herman Jul 13 '15 at 18:05
  • @fge with "side effect" I meant that `source.putSomething(sth)` will make a DB call, or a web service call, or any other IO operation. – herman Jul 13 '15 at 18:07
  • Updating the source to contain the default value is a side-effect no matter how you do it (unless maybe monads are involved...). There is no way to do it without side-effects. – Jacob Zimmerman Jul 13 '15 at 19:05
  • @JacobZimmerman I get that, but it just "feels" wrong to put this in a function that is supplied to `orElseGet()`. That function is supposed to generate a value in case the `Optional` is empty, nothing else. Maybe I'm just too picky but it doesn't seem to be FP style. – herman Jul 13 '15 at 21:16
  • @herman that's true. Heck, you should almost never write a lambda with more than one line, if you can help it. – Jacob Zimmerman Jul 14 '15 at 10:54

3 Answers3

5

You could follow the way Java does this with Map.computeIfAbsent()

which takes a second parameter which is a function on how to compute and insert the record:

So your code would become:

Something something = source.computeIfAbsent(sth, (k)->provideDefaultValue());

An advantage of using a lambda to compute the default instead of just passing it in, is the lambda will only be evaluated if it needs to be so if computing the default is expensive, you only have to pay it when you need it.

dkatzel
  • 31,188
  • 3
  • 63
  • 67
  • 1
    @herman I know your `source` doesn't implement `Map` my suggestion was to add a `computeIfAbsent()` method to your object. The difference is that your computeIfAbsent can be made an atomic – dkatzel Jul 13 '15 at 19:44
  • Oh, I see now, you mean like just adding a `Supplier` as a parameter to the `retrieveSomething()` method and letting the source update its value. This is similar to what Makoto suggested and I thought of it myself briefly, but 1) I'd prefer the methods in the service to do a single thing only, and 2) this actually avoids the use of `Optional`, but I'm interested in the case where a method may return an empty `Optional` and something need to happen besides just returning a default value, and without resorting to imperative style. So let's just suppose that I can't change `source`. – herman Jul 13 '15 at 21:08
  • This answer is getting the most upvotes, seems like people are telling me not to use `Optional` at all. However, return values are exactly the case that `Optional` was meant to be used for. – herman Jul 14 '15 at 09:19
  • @herman: `Optional` is the ideal return value for *queries*, which are not meant to modify the original data source. – Holger Jul 15 '15 at 07:40
  • @Holger in my sample code, the query returns an `Optional` and doesn't try to modify the data source. Instead, it's the code that calls the query that wants to update the data source in reaction to receiving an empty `Optional` from the query. It's this answer that suggests to move the update *inside* the query (and thereby avoid `Optional`). – herman Jul 15 '15 at 09:34
  • 1
    @herman: this answer doesn’t tell you to avoid `Optional`. It’s only obsolete for the use case where an absent value triggers a creation and hence, there’s always a present value. Your source may still have a pure query method returning an `Optional` as well. – Holger Jul 15 '15 at 09:53
0

From a conceptual standpoint, you use the optional pattern to deal with the absence of a return value. This means, if your source instance doesn't contain a value for you to use, you have the choice of providing a default value to use in its place.

It is not advised to modify the Optional directly to provide its value; that instance may be temporal and will differ on subsequent calls to retrieve it.

Since a function call truly governs what's returned by that Optional, the only approach you have if you truly want to go down this route is to modify how that value is computed. This really should be done from within the function providing the Optional, but it could be done outside of it if necessary.

Since there's not enough code structure here to truly write up some example, I will describe the steps:

  • Outside of the method providing the Optional, you write the same closure as you did before, with the side effect of adjusting the value used to compute the original Optional. This is likely a field of some sort.

  • Inside of the method providing the Optional, you ensure that you don't expose provideDefaultValue anywhere else (since they won't need it), and use a boolean conditional before you package the Optional.

    return value == null ? Optional.of(provideDefaultValue()) : Optional.of(value);
    

    ...but that really defeats the purpose of the Optional, as you're still doing a null check.

    A slightly better approach to the above would be to compute value in such a way that it was either itself or the default value, and return the Optional of that...

    value = computeValue();
    if(null == value) {
        value = provideDefaultValue();
    }
    return Optional.of(value);
    

    ...but again, seriously defeating the purpose of Optional, as you're doing null checks.

Community
  • 1
  • 1
Makoto
  • 104,088
  • 27
  • 192
  • 230
  • I wasn't planning to update the `Optional` itself. `source` isn't the `Optional`, `source` is e.g. an object that retrieves data from a DB or a REST service in its `retrieveSomething()` method. So if the DB (or REST service) doesn't have the data and returns an empty optional, I want to return a default value but also update the DB (or REST service) with that default value. – herman Jul 13 '15 at 18:01
  • In that case, you want to do that from within the service (although the use of `Optional` is dodgy at that point if you can guarantee a value always comes back). My answer above goes into detail on that. – Makoto Jul 13 '15 at 18:02
0

An answer I came up with myself, which I'm not entirely satisfied with, but may serve as an example of what I'm looking for:

I could implement something similar to a Pair<V1,V2> class and then map the Optional<Something> to a Pair<Something, Boolean> where the Boolean value would indicate whether or not the value was a generated default:

Pair<Something, Boolean> somethingMaybeDefault = 
    source.retrieveSomething()
          .map(sth -> new Pair<Something, Boolean>(sth, false))
          .orElseGet(() -> new Pair<Something, Boolean>(provideDefaultValue(), true));

Then I'd update in case the boolean is false:

if (somethingMaybeDefault.value2()) {
    source.putSomething(somethingMaybeDefault.value1());
}

And finally return the new value:

return somethingMaybeDefault.value1();

Of course, this uses imperative style for the update, but at least the functions remain pure. I'm not sure this is the best possible answer though.

herman
  • 11,740
  • 5
  • 47
  • 58
  • don't worry too much. we are mixing imperative and functional anyway. it's fine as long as we know the exact execution semantics. We may even question the pure FP people whether they have good solutions to side effect, or mere caching; their answer may seem grotesque to us imperative people. – ZhongYu Jul 13 '15 at 22:13
  • @bayou.io so for the generic case (assuming we can't change `source` to let the `retrieveSomething()` method do the update itself, avoiding the use of `Optional`), you think I should accept my own answer? – herman Jul 13 '15 at 22:30
  • I think you should just use plain old java:) imperative code branching on the optional returned from `source.retrieveSomething()` – ZhongYu Jul 13 '15 at 22:46