I have the following Scala snippet. In order to solve my given problem, I “cheat” a little and use a var — essentially a non-final, mutable data type. Its value is updated at each iteration through the loop. I’ve spent quite a bit of time trying to figure out how to do this using only recursion, and immutable data types and lists.
Original snippet:
def countChange_sort(money: Int, coins: List[Int]): Int =
if (coins.isEmpty || money < 0)
0
else if (coins.tail.isEmpty && money % coins.head != 0) {
0
} else if (coins.tail.isEmpty && money % coins.head == 0 || money == 0) {
1
} else {
-- redacted --
}
}
Essentially, are there any basic techniques I can use to eliminate the i and especially the accumulating cnt variables?
Thanks!!
There are lots of different ways to solve problems in functional style. Often you start by analysing the problem in a different way than you would when designing an imperative algorithm, so writing an imperative algorithm and then “converting” it to a functional one doesn’t produce very natural functional algorithms (and you often miss out on lots of the potential benefits of functional style). But when you’re an experienced imperative programmer just starting out with functional programming, that’s all you’ve got, and it is a good way to begin getting your head around the new concepts. So here’s how you can approach “converting” such a function as the one you wrote to functional style in a fairly uncreative way (i.e. not coming up with a different algorithm).
Lets just consider the
elseexpression since the rest is fine.Functional style has no loops, so if you need run a block of code a number of times (the body of the imperative loop), that block of code must be a function. Often the function is a simple non-recursive one, and you call a higher-order function such as map or fold to do the actual recursion, but I’m going to presume you need the practice thinking recursively and want to see it explicitly. The loop condition is calculated from the quantities you have at hand in the loop body, so we just have the loop-replacement function recursively invoke itself depending on exactly the same condition:
Information is only communicated to a function through its input arguments or through its definition, and communicated from a function through its return value.
The information that enters a function through its definition is constant when the function is created (either at compile time, or at runtime when the closure is created). Doesn’t sound very useful for the information contained in
cntandi, which needs to be different on each call. So they obviously need to be passed in as arguments:But we want to use the final value of
cntafter the function call. If information is only communicated fromloopthrough its return value, then we can only get the last value ofcntby havingloopreturn it. That’s pretty easy:coins,money, andcountChange_sortare examples of information “entering a function through its definition”.coinsandmoneyare even “variable”, but they’re constant at the point whenloopis defined. If you wanted to moveloopout of the body ofcountChange_sortto become a stand-alone function, you would have to makecoinsandmoneyadditional arguments; they would be passed in from the top-level call incountChange_sort, and then passed down unmodified in each recursive call insideloop. That would still makeloopdependent oncountChange_sortitself though (as well as the arithmetic operators*and-!), so you never really get away from having the function know about external things that don’t come into it through its arguments.Looking pretty good. But we’re still using assignment statements inside
loop, which isn’t right. However all we do is assign new values tocntandiand then pass them to a recursive invocation ofloop, so those assignments can be easily removed:Now there are some obvious simplifications, because we’re not really doing anything at all with the mutable
cntandiother than initialising them, and then passing their initial value, assigning tocntonce and then immediately returning it. So we can (finally) get rid of the mutablecntandientirely:And we’re done! No side effects in sight!
Note that I haven’t thought much at all about what your algorithm actually does (I have made no attempt to even figure out whether it’s actually correct, though I presume it is). All I’ve done is straightforwardly applied the general principle that information only enters a function through its arguments and leaves through its return values; all mutable state accessible to an expression is really extra hidden inputs and hidden outputs of the expression. Making them immutable explicit inputs and outputs, and then allows you to prune away unneeded ones. For example,
idoesn’t need to be included in the return value fromloopbecause it’s not actually needed by anything, so the conversion to functional style has made it clear that it’s purely internal toloop, whereas you had to actually read the code of the imperative version to deduce this.cntandiare what is known as accumulators. Accumulators aren’t anything special, they’re just ordinary arguments; the term only refers to how they are used. Basically, if your algorithm needs to keep track of some data as it goes, you can introduce an accumulator parameter so that each recursive call can “pass forward” the data from what has been done so far. They often fill the role that local temporary mutable variables fill in imperative algorithms.It’s quite a common pattern for the return value of a recursive function to be the value of an accumulator parameter once it is determined that there’s no more work left to do, as happens with
cntin this case.Note that these sort of techniques don’t necessarily produce good functional code, but it’s very easy to convert functions implemented using “local” mutable state to functional style using this technique. Pervasive non-local use of mutability, such as is typical of most traditional OO programs, is harder to convert like this; you can do it, but you tend to have to modify the entire program at once, and the resulting functions have large numbers of extra arguments (explicitly exposing all the hidden data-flow that was present in original program).