3

In a lot of web articles, functional programming is presented as avoiding all kinds of variables reassignment and so promoting only "final" variables at least for especially just better reading.

Most of them are taken the sample of a poor loop with a counter variable incrementing. (like the famous i++ or x = x + 1. Here an article of Uncle Bob illustrating it: FP Episode 1

Hence, these articles signal that counting on mutable variables very often leads to side effects, and especially prevent what we call "Referential transparency" and therefore, makes harder to construct a program running on multi-threads or better, multi-processors.

My question is: As we all know, i++ is generally a thread LOCAL variable, so no issues can occur even with concurrent processing.

Why to choose an example like a loop with local variable as a drawback of assignments, and permit directly to conclude that concurrency programming is risking?? These both things are strictly unrelated to me.

Why not, in order to be more clear, choose a reassignment of global variable (or field object) that is clearly the enemy of concurrent programming without overusing all boilerplates of locks as Java does.

I really think that this loop sample isn't the best illustration to transmit benefits of functional programming to imperative programmers guys.

Furthermore, it leads to confusion with "noob" functional programmers since Scala for instance uses a lot of the while loop pattern as in List.scala class:

override def take(n: Int): List[A] = {
    val b = new ListBuffer[A]
    var i = 0
    var these = this
    while (!these.isEmpty && i < n) {  
      i += 1   // reassignment here
      b += these.head
      these = these.tail
    }
    if (these.isEmpty) this
    else b.toList
  } 
5
  • 5
    That's why scala is a hybrid language - it uses whatever best suits the task, with a bias towards functional style. The example with mutable counter is just like you said - a poor example. There's nothing that makes mutable state bad in every situation (unless you're a purist, and it looks like they're right after all), it's just difficult to draw the line that separates 'good mutable state' and 'bad mutable state' and hence it's better to avoid it at all. The less exceptions you make, the easier it is to understand what you're trying to achieve. Commented Dec 24, 2012 at 13:07
  • also this probably isn't the best question to ask on SO :) Commented Dec 24, 2012 at 13:07
  • 3
    I think Odersky himself said that they aim for API's to be functional but internal code is what is most optimal for a specific implementation. Commented Dec 24, 2012 at 13:08
  • @soulcheck, it doesn't really solicit opinion. What I may ignore is the special case of multi-core programming. I'm currently searching whether in any specific cases, local variable may be shared by both processors... and so explaining the loop sample and the risk that it would involve. By the way, I agree with your first comment :) Commented Dec 24, 2012 at 13:09
  • 4
    Using a mutable counter in a loop will make it harder to parallelize the loop. If you had used a map or a fold we could have substituted it for a parallel map or fold. Commented Dec 24, 2012 at 15:32

2 Answers 2

7

I think Odersky himself said that they aim for API's to be functional but internal code is what is most optimal for a specific implementation. So you probably should not search Scala library internals for "good use of Scala" or "great examples of FP".

Using mutable state to hold indexes (for example) is also really error-prone. So you should aim to use operations to whole collections (filter/map/flatMap etc) so you never need to worry about stuff like "index out of bounds". Still, these operations often cause lot's of temporary/intermediary collections to be created, and so they cause extra garbage collecting. This usually doesn't matter for 99% of programs but again, these are optimized as much as possible inside Scala library internals.

So yes, there's a place for everything but practicing to "survive" with as little mutable state as possible is good practice for single-threaded programs too, because of fewer possible places for bugs, easier testability, better readability.

Sign up to request clarification or add additional context in comments.

Comments

6

In a simple loop, there's no problem--it isn't ever a problem of concurrency, and you can probably keep track of the variables. Well, maybe.

// Take the first n items that pass p
def takeGood(n: Int)(p: A => Boolean): List[A] = {
  val b = new ListBuffer[A]
  var these = this
  var i = 0
  while (!these.isEmpty && i < n) {
    i += 1
    if (p(these.head)) b += these.head
    these = these.tail
  }
  b.toList
}

Um, except this doesn't work--we've incremented i on every loop, not just on the ones we take.

If you use recursion, it becomes more obvious, at least, what you're doing:

def takeGood[A](these: List[A], n: Int)(p: A => Boolean)(b: ListBuffer[A] = new ListBuffer[A]): List[A] = {
  if (these.isEmpty || n <= 0) b.toList
  else if (p(these.head)) takeGood(these.tail, n-1)(p)({ b += these.head; b })
  else takeGood(these.tail, n)(p)(b)
}

So there can be advantages to using functional style even when concurrency is not in play: sometimes (especially with loops) it makes the looping much more explicit and therefore reduces the chance of error.

Concurrency brings additional advantages, as being consistent but out of date is usually much better than being inconsistent or deadlocking. But that's not what is shown in a while-loop with iterator.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.