Reputation: 15385
I have a sealed trait which is implemented by 3 objects
sealed trait MyTrait {
...
}
object A extends MyTrait { ... }
object B extends MyTrait { ... }
object C extends MyTrait { ... }
I'm using Scalaz's validation mechanism wherein the apply methods of the objects A, B and C return a validated type. the Objects A, B and C does contain some logic and I want to apply this logic sequentially, i.e., I want to first apply A and check what the result of A is and based on it, decide if I want to call B or just return the validated result. I want to repeat this until I hit C after which I just return whatever I get as a result of calling C.
Currently I have a static approach where I first call A, pass the result of A to a utility method and check for the result and then call B.
def apply(request: Request): Validated[Result] = {
val vResultA = run(request, A)
val vResultB = if (isResultOk(vResultA)) run(request, B) else vResultA
if (isResultOk(vResultB)) run(request, C) else vResultB
}
Is there a better way to do this? Any suggestions or any patterns that I can apply?
Upvotes: 0
Views: 1427
Reputation: 15552
We will define succeeded results = results that are OK, and failed results = results that are not OK.
First, A
, B
, and C
are all objects extending MyTrait
. Therefore, they can be grouped into an Array or a List of MyTrait
.
val objects = Array(A, B, C) /* You can use List instead if you want. */
Then the type of objects
is Array[MyTrait]
.
Next, we have to iterate on this Array.
However, just calling map
on this Array continues mapping even if the previous isResultOk()
is false
.
Therefore, we will use Stream
instead of Array
.
Let's see how using Stream
can stop calling map
if some condition is satisfied.
Array(1, 2, 3, 4, 5).map(i => {
println(i)
i + 100
}).takeWhile(_ <= 103).foreach(println(_))
The output of the above code will be:
1
2
3
4
5
101
102
103
So, map()
ends, and then takeWhile()
ends -- takeWhile()
does not affect calling map()
.
However, if we do the same operations on the Stream
,
Array(1, 2, 3, 4, 5).toStream.map(i => {
println(i)
i + 100
}).takeWhile(_ <= 103).foreach(println(_))
The output will be:
1
101
2
102
3
103
4
So the calling will be map() -> takeWhile() -> foreach() -> map() -> takeWhile() -> ...
At the end, 4 is printed, and 4 + 100 = 104 > 103 will be cut in takeWhile()
.
The following elements will be not accessed further.
So, do we have to use takeWhile?
objects.toStream.map(run(request, _)).takeWhile(isResultOk(_))
This will get rid of failed results, even though we need the first failed result if failure occured.
(i.e. This will make a problem if there is any result that is not OK.)
How about the opposite function dropWhile()
?
objects.toStream.map(run(request, _)).dropWhile(isResultOk(_))
This will get rid of all succeeded results, even though all results are succeeded.
(i.e. This will make a problem if all results are OK.)
So, we will use span()
.
c.span(p)
= (c.takeWhile(p), c.dropWhile(p))
We will test if there are results that are not OK.
If there is a result that is not OK, then we will return the first such result.
Otherwise, we will return the last result that is OK.
val (succ, fail) = objects.toStream.map(run(request, _)).span(isResultOk(_))
fail.headOption.getOrElse(succ.last)
fail.headOption
will return Some(fail's first element)
if fail
is not empty, otherwise None
.
In summary,
val objects = Array(A, B, C)
def apply(request: Request): Validated[Result] = {
val (succ, fail) = objects.toStream.map(run(request, _)).span(isResultOk(_))
fail.headOption.getOrElse(succ.last)
}
Upvotes: 3