24

Is having mutable local variables in a function that are only used internally, (e.g. the function has no side effects, at least not intentionally) still considered "non functional"?

e.g. in the "Functional programming with Scala" course style check considers any var usage as bad

My question, if the function has no side effects, is writing imperative style code still discouraged?

e.g. instead of using tail recursion with the accumulator pattern, what's wrong with doing a local for loop and creating a local mutable ListBuffer and adding to it, as long as the input is not changed?

If the answer is "yes, they are always discouraged, even if there is no side effects" then what is the reason?

3
  • 3
    All the advice, exhortations etc. about the topic that I've ever heard refer to shared mutable state as the source of complexity. Is that course intended to be consumed by beginners only? Then it's probably a well-intended deliberate oversimplification. Apr 25, 2013 at 16:20
  • 3
    @KilianFoth: Shared mutable state is a problem in multithreaded contexts, but non-shared mutable state can lead to programs being hard to reason about as well. Apr 25, 2013 at 17:38
  • 1
    I think using a local mutable variable is not necessarily bad practice, but it is not "functional style": I think the purpose of the Scala course (which I took during last fall) is to teach you programming in a functional style. Once you can clearly distinguish between functional and imperative style you can decide when to use which (in case your programming language allows both). var is always non-functional. Scala has lazy vals and tail recursion optimization, which allow to avoid vars completely.
    – Giorgio
    Apr 26, 2013 at 5:28

6 Answers 6

17

The one thing that is unequivocally bad practice here is claiming that something is a pure function when it isn't.

If mutable variables are used in a way that is truly and completely self-contained, the function is externally pure and everyone is happy. Haskell in fact supports this explicitly, with the type system even ensuring that mutable references can't be used outside the function that creates them.

That said, I think talking about "side effects" is not the best way to look at it (and is why I said "pure" above). Anything that creates a dependency between the function and external state makes things harder to reason about, and that includes things like knowing the current time or using concealed mutable state in a non-thread-safe way.

19

The problem isn't mutability per se, it's a lack of referential transparency.

A referentially transparent thing and a reference to it always have to be equal, so a referentially transparent function will always return the same results for a given set of inputs and a referentially transparent "variable" is really a value rather than a variable, since it can't change. You can make a referentially transparent function that has a mutable variable inside; that isn't a problem. It might be harder to guarantee the function is referentially transparent, though, depending on what you're doing.

There's one instance I can think of where mutability has to be used to do something that is very functional: memoization. Memoization is caching values from a function, so they don't have to be recomputed; it's referentially transparent, but it does use mutation.

But in general referential transparency and immutability go together, other than a local mutable variable in a referentially transparent function and memoization, I'm not sure there are any other examples where this is not the case.

5
  • 5
    Your point about memoization is very good. Note that Haskell strongly emphasizes referential transparency for programming, but the memoization-like behavior of lazy evaluation involves a staggering amount of mutation being done by the language runtime behind the scenes. Apr 25, 2013 at 19:08
  • 1
    @C. A. McCann: I think what you say is very important: in a functional language the runtime can use mutation to optimize the computation but there is not construct in the language that allows the programmer to use mutation. Another example is a while loop with a loop variable: in Haskell you can write a tail recursive function that may be implemented with a mutable variable (to avoid using the stack), but what the programmer sees are immutable function arguments that are passed from one call to the next.
    – Giorgio
    Apr 26, 2013 at 5:21
  • @Michael Shaw: +1 for "The problem isn't mutability per se, it's a lack of referential transparency." Maybe you can cite the Clean language in which you have uniqueness types: these allow mutability but still guarantee referential transparency.
    – Giorgio
    Apr 26, 2013 at 8:33
  • @Giorgio: I don't really know anything about Clean, although I've heard it mentioned from time to time. Maybe I should look into it. Apr 26, 2013 at 12:07
  • @Michael Shaw: I do not know very much about Clean, but I know that it uses uniqueness types to ensure referential transparency. Basically, you can modify a data object provided that after the modification you have no references to the old value. IMO this illustrates your point: referential transparency is the most important point, and immutability is only one possible way of ensuring it.
    – Giorgio
    Apr 26, 2013 at 14:51
9

It's not really good to boil this down to "good practice" vs "bad practice". Scala supports mutable values because they solve certain problems much better than immutable values, namely those that are iterative in nature.

For perspective, I'm fairly sure that via CanBuildFrom almost all immutable structures provided by scala do some sort of mutation internally. The point is that what they expose is immutable. Keeping as many values immutable as possible helps make the program easier to reason about and less error prone.

This doesn't mean that you necessarily need to avoid mutable structures and values internally when you have a problem that is better suited to mutability.

With that in mind, a lot of problems that typically require mutable variables (such as looping) can be solved better with a lot of the higher order functions that languages like Scala provide (map/filter/fold). Be aware of those.

1
  • 2
    Yep, I almost never need a for loop when using Scala's collections. map, filter, foldLeft and forEach do the trick most of the time, but when they don't, being able to feel I'm "OK" to reverting to brute force imperative code is nice. (as long as there are no side effects of course)
    – Eran Medan
    Apr 25, 2013 at 15:55
3

Aside from potential issues with thread safety, you also typically lose a lot of type safety. Imperative loops have a return type of Unit and can take pretty much any expression for inputs. Higher-order functions and even recursion have much more precise semantics and types.

You also have many more options for functional container processing than with imperative loops. With imperative, you basically have for, while, and minor variations on those two like do...while and foreach.

In functional, you have aggregate, count, filter, find, flatMap, fold, groupBy, lastIndexWhere, map, maxBy, minBy, partition, scan, sortBy, sortWith, span, and takeWhile, just to name a few more common ones from Scala's standard library. When you become accustomed to having those available, imperative for loops seem too basic in comparison.

The only real reason to use local mutability is very occasionally for performance.

0
2

I would say it is mostly ok. What's more, generating structures this way might be a good way to improve performances in some cases. Clojure has takled this problem by providing Transient data structures.

The basic idea is to allow local mutations in a limited scope, and then to freeze the structure before returning it. This way, your user can still reason about your code as if it were pure, but you are able to perform in place transformations when you need to.

As the link says:

If a tree falls in the woods, does it make a sound? If a pure function mutates some local data in order to produce an immutable return value, is that ok?

2

Having no local mutable variables does have one advantage--it makes the function more friendly towards threads.

I got burned by such a local variable (not in my code, nor did I have the source) causing a low-probability data corruption. Thread safety wasn't mentioned one way or another, there was no state that persisted across calls and there were no side effects. It didn't occur to me that it might not be thread safe, chasing a 1 in 100,000 random data corruption is a royal pain.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.