I would second jacobz_20’s sentiment. Declarative vs Imperative is very rarely black and white, but rather on a spectrum, a grayscale.
To repeat jacobz_20, the central crux is “what” vs “how”. But every example is relative.
The more the program reads like an expression of business logic than instructions for a computer, generally the more declarative.
Like I said, declarative or imperative is not black and white. Almost every declarative has imperative traces.
Here’s some examples of your function, going from (most imperative and least declarative) to (least imperative and most declarative)
fun getUncleBobVeryImperative() : String {
// notice the meaningless variable names
val c = ArrayList<String>()
// notice the tedious operations just to get to the point
c.add("aunt alice")
c.add("uncle bob")
c.add("joe plumber")
// notice that loop index is just a control flow cog that we wouldn't care about if we didn't have to
var i = 0
// more crappy variable names
val r = ArrayList<String>()
// You have to tell the program how to do everything, like when to stop looping
while (i < c.size) {
val p = c[i]
// i++ means nothing to the business logic, but we write it anyway
i++
// finally a part of the program we are actually interested in - is uncle bob here or not?
if (p == "uncle bob") {
// see how this is wide away from the return statement, when in fact it is the key moment we are looking for
r.add(p)
// tell the loop how to stop
break
}
}
// manually figure out if sought out data is there or not
// pull out the user with cryptic [0], whatever that means. What does "[0]" have to do with my uncle bob?
return if (r.size > 1) r[0] else ""
}
fun getUncleBobJavaFunctional() : String {
// declare your collection all at once - no "add()" invocations
// Still, what do "Arrays" have to do with anything?
// much better more meaningful variable name
val people = Arrays.asList("aunt alice", "uncle bob", "joe plumber")
// what is a stream? why do I care what a stream is?
return people.stream()
// It's obvious we are filtering something... why specify the parameter? can't it be implicit?
// I want to see if uncle bob is in the collection of people. What does that have to do with the word 'filter'?
.filter { it -> it == "uncle bob" }
// "findAny" sounds closer to what we are actually trying to do - find any Bob. (contrast with "filter")
.findAny()
// "orElse" is fairly declarative, like saying: find "uncle bob" or else ""
// "Optional.get" would be less declarative, because it leaks the implementation tedium without lending extra
// meaning to the business logic
.orElse("")
}
fun getUncleBobKotlinNaive() : String {
// flawless declarative syntax for creating a list
val people = listOf("aunt alice", "uncle bob", "joe plumber")
// no stream, why do we care what a stream is? just filter. awesome.
// filter is still not perfectly declarative
// it is a declarative way to filter out results in a collection
// it is an imperative way to see if uncle bob *exists* in a collection
// implicit parameter "it" is awesome
// "first" of "firstOrNull" is pretty declarative, but mentioning "null" at all leaks the fact we are still giving
// instructions to a computer
return people.filter { it == "uncle bob" }.firstOrNull() ?: ""
}
fun getUncleBobKotlinIdiomatic() : String {
// declare people
val people = listOf("aunt alice", "uncle bob", "joe plumber")
// key phrase "find" is more declarative than "filter", we want to find bob, not filter a collection
// It is still imperative though, because we don't ACTUALLY want to find bob, we want to know if he is in the list
// the ?: is highly imperative, leaking the 'semantics of implementation' with handling null explicitly
return people.find { it == "uncle bob" } ?: ""
}
fun getUncleBobKotlinWiseAss() : String {
// declare people
val people = listOf("aunt alice", "uncle bob", "joe plumber")
// declare bob
val bob = "uncle bob"
// if bob is in people, then return bob, otherwise return ""
// it is VERY difficult to make something more declarative without actually making things more complicated
// there's no while(), there's no stream(), no filter(), no find(), we just want to know if bob is in people
// we ask if bob is in people, and that's what we get. Declarative rather than imperative.
return if (bob in people) bob else ""
}