public Void traverseQuickestRoute(){ // Void return-type from interface
findShortCutThroughWoods()
.map(WoodsShortCut::getTerrainDifficulty)
.ifPresent(this::walkThroughForestPath) // return in this case
if(isBikePresent()){
return cycleQuickestRoute()
}
....
}
Is there a way to exit the method at the ifPresent
?
In case it is not possible, for other people with similar use-cases: I see two alternatives
Optional<MappedRoute> woodsShortCut = findShortCutThroughWoods();
if(woodsShortCut.isPresent()){
TerrainDifficulty terrainDifficulty = woodsShortCut.get().getTerrainDifficulty();
return walkThroughForrestPath(terrainDifficulty);
}
This feels more ugly than it needs to be and combines if/else with functional programming.
A chain of orElseGet(...)
throughout the method does not look as nice, but is also a possibility.
CodePudding user response:
return
is a control statement. Neither lambdas (arrow notation), nor method refs (WoodsShortcut::getTerrainDifficulty
) support the idea of control statements that move control to outside of themselves.
Thus, the answer is a rather trivial: Nope.
You have to think of the stream 'pipeline' as the thing you're working on. So, the question could be said differently: Can I instead change this code so that I can modify how this one pipeline operation works (everything starting at findShortCut()
to the semicolon at the end of all the method invokes you do on the stream/optional), and then make this one pipeline operation the whole method.
Thus, the answer is: orElseGet
is probably it.
Disappointing, perhaps. 'functional' does not strike me as the right answer here. The problem is, there are things for/if/while loops can do that 'functional' cannot do. So, if you are faced with a problem that is simpler to tackle using 'a thing that for/if/while is good at but functional is bad at', then it is probably a better plan to just use for/if/while then.
One of the core things lambdas can't do are about the transparencies. Lambdas are non-transparant in regards to these 3:
- Checked exception throwing.
try { list.forEach(x -> throw new IOException()); } catch (IOException e) {}
isn't legal even though your human brain can trivially tell it should be fine. - (Mutable) local variables.
int x = 5; list.forEach(y -> x = y);
does not work. Often there are ways around this (list.mapToInt(Integer::intValue).sum()
in this example), but not always. - Control flow.
list.forEach(y -> {if (y < 0) return y;});
does not work.
So, keep in mind, you really have only 2 options:
- Continually retrain yourself to not think in terms of such control flow. You find
orElseGet
'not as nice'. I concur, but if you really want to blanket apply functional to as many places as you can possibly apply it, the whole notion of control flow out of a lambda needs not be your go-to plan, and you definitely can't keep thinking 'this code is not particularly nice because it would be simpler if I could control flow out', you're going to be depressed all day programming in this style. The day you never even think about it anymore is the day you have succeeded in retraining yourself to 'think more functional', so to speak. - Stop thinking that 'functional is always better'. Given that there are so many situations where their downsides are so significant, perhaps it is not a good idea to pre-suppose that the lambda/methodref based solution must somehow be superior. Apply what seems correct. That should often be "Actually just a plain old for loop is fine. Better than fine; it's the right, most elegant1 answer here".
[1] "This code is elegant" is, of course, a non-falsifiable statement. It's like saying "The Mona Lisa is a pretty painting". You can't make a logical argument to prove this and it is insanity to try. "This code is elegant" boils down to saying "I think it is prettier", it cannot boil down to an objective fact. That also means in team situations there's no point in debating such things. Either everybody gets to decide what 'elegant' is (hold a poll, maybe?), or you install a dictator that decrees what elegance is. If you want to fix that and have meaningful debate, the term 'elegant' needs to be defined in terms of objective, falsifiable statements. I would posit that things like:
- in face of expectable future change requests, this style is easier to modify
- A casual glance at code leaves a first impression. Whichever style has the property that this first impression is accurate - is better (in other words, code that confuses or misleads the casual glancer is bad). Said even more differently: Code that really needs comments to avoid confusion is worse than code that is self-evident.
- this code looks familiar to a wide array of java programmers
- this code consists of fewer AST nodes (the more accurate from of 'fewer lines = better')
- this code has simpler semantic hierarchy (i.e. fewer indents)
Those are the kinds of things that should define 'elegance'. Under almost all of those definitions, 'an if
statement' is as good or better in this specific case!
CodePudding user response:
For example:
public Void traverseQuickestRoute() {
return findShortCutThroughWoods()
.map(WoodsShortCut::getTerrainDifficulty)
.map(this::walkThroughForestPath)
.orElseGet(() -> { if (isBikePresent()) { return cycleQuickestRoute(); } });
}
CodePudding user response:
There is Optional#ifPresentOrElse
with an extra Runnable
for the else case. Since java 9.
public Void traverseQuickestRoute() { // Void return-type from interface
findShortCutThroughWoods()
.map(WoodsShortCut::getTerrainDifficulty)
.ifPresentOrElse(this::walkThroughForestPath,
this::alternative);
return null;
}
private void alternative() {
if (isBikePresent()) {
return cycleQuickestRoute()
}
...
}
I would split the method as above. Though for short code () -> { ... }
might be readable.