9

I can see the benefits of mutable vs immutable objects like immutable objects take away lot of hard to troubleshoot issues in multi threaded programming due to shared and writeable state. On the contrary, mutable objects help to deal with identity of object rather than creating new copy every time and thus also improve performance and memory usage especially for larger objects.

One thing I am trying to understand is what can go wrong in having mutable objects in context of functional programming. Like one of points told to me is that the result of calling functions in different order is not deterministic.

I am looking for real concrete example where it is very apparent what can go wrong using mutable object in function programming. Basically if it is bad, it is bad irrespective of OO or functional programming paradigm, right ?

I believe below my own statement itself answers this question. But still I need some example so that I can feel it more naturally.

OO helps to manage dependency and write easier and maintainable program with the aid of tools like encapsulation, polymorphism etc.

Functional programming also have same motive of promoting maintainable code but by using style which eliminates the need for using OO tools and techniques - one of which I believe is by minimizing side effects, pure function etc.

9
  • 1
    @Ruben i'd say most functional languages do allow mutable variable, but make it different to use them e.g. mutable variables have a different type
    – jk.
    Apr 30, 2018 at 8:22
  • 1
    I think you may ve mixed immutable and mutable in your first paragraph?
    – jk.
    Apr 30, 2018 at 8:24
  • 1
    @jk., he certainly did. Edited to correct that.
    – David Arno
    Apr 30, 2018 at 8:50
  • 6
    @Ruben Functional programming is a paradigm. As such it doesn't require a functional programming language. And some fp languages such as F# have this feature.
    – Christophe
    Apr 30, 2018 at 9:12
  • 1
    @Ruben no specifically I was thinking of Mvars in haskell hackage.haskell.org/package/base-4.9.1.0/docs/… different languages have different solutions of course or IORefs hackage.haskell.org/package/base-4.11.1.0/docs/Data-IORef.html though of course you would use both from within monads
    – jk.
    Apr 30, 2018 at 9:29

7 Answers 7

7

I think the importance is best demonstrated by comparing to an OO approach

eg, say we have an object

Order
{
    string Status {get;set;}
    Purchase()
    {
        this.Status = "Purchased";
    }
}

In the OO paradigm the method is attached to the data, and it makes sense for that data to be mutated by the method.

var order = new Order();
order.Purchase();
Console.WriteLine(order.Status); // "Purchased"

In the Functional Paradigm we define a result in terms of the function. a purchased order IS the result of the purchase function applied to an order. This implies a few things which we need to be sure of

var order = new Order(); //this is a 'new order'
var purchasedOrder = purchase(order); // this is a 'purchased order'
Console.WriteLine(order.Status); // "New" order is still a 'new order'

Would you expect order.Status == "Purchased"?

It also implies that our functions are idempotent. ie. running them twice should produce the same result each time.

var order = new Order(); //new order
var purchasedOrder = purchase(order); //purchased order
var purchasedOrder2 = purchase(order); //another purchased order
var purchasedOrder = purchase(purchasedOrder); //error! cant purchase an order twice

If order was changed by the purchase function, purchasedOrder2 would fail.

By defining things as results of functions it allows us to use those results without actually calculating them. Which in programming terms is deferred execution.

This can be handy in of itself, but once we are unsure about when a function will actually happen AND we are fine about that, we can leverage parallel processing much more than we can in an OO paradigm.

We know that running a function wont affect the results of another function; so we can leave the computer to execute them in any order it chooses, using as many threads as it likes.

If a function mutates its input we have to be much more careful about such things.

4
  • thanks !! very helpful. So new implementation of purchase would look like Order Purchase() { return new Order(Status = "Purchased") } so that status is read only field. ? Again why this practice is more relevant in context of function programming paradigm ? Benefits you mentioned can be seen in OO programming as well, right ? Apr 30, 2018 at 11:38
  • in OO you would expect object.Purchase() to modify the object. You could make it immutable, but then why not move to a full Functional paradigm
    – Ewan
    Apr 30, 2018 at 11:42
  • I think problem am having to visualize because am pure c# developer which is object oriented by nature. So what you saying in language which embrace functional programming will not require 'Purchase()' function returning purchased order to be attached with any class or object, right ? Apr 30, 2018 at 11:55
  • 3
    you can write functional c# change your object to a struct, make it immutable and write a Func<Order, Order> Purchase
    – Ewan
    Apr 30, 2018 at 12:02
14

The key to understanding why immutable objects are beneficial doesn't really lie in trying to find concrete examples in functional code. Since most functional code is written using functional languages, and most functional languages are immutable by default, the very nature of the paradigm is designed to avoid what you are looking for, from happening.

The key thing to ask is, what is that benefit of immutability? The answer is, it avoids complexity. Say we have two variables, x and y. Both start with the value of 1. y though doubles every 13 seconds. What will the value of each of them be in 20 days time? x will be 1. That's easy. It would take effort though to work out y as it's way more complex. What time of day in 20 days time? Do I have to take daylight saving into account? The complexity of y versus x is just so much more.

And this occurs in real code too. Every time you add a mutating value to the mix, that becomes another complex value for you to hold and calculate in your head, or on paper, when trying to write, read or debug the code. The more complexity, the greater the chance of you making a mistake and introducing a bug. The code is hard to write; hard to read; hard to debug: the code is hard to get right.

Mutability isn't bad though. A program with zero mutability can have no outcome, which is pretty useless. Even if the mutability is to write a result to screen, disk or whatever, it needs to be there. What is bad is needless complexity. One of the simplest ways to reduce complexity is to make things immutable by default and only make them mutable when needed, due to performance or functional reasons.

17
  • 5
    "one of the simplest ways to reduce complexity is to make things immutable by default and only make them mutable when needed": Very nice and concise summary.
    – Giorgio
    Apr 30, 2018 at 9:26
  • 2
    @DavidArno The complexity you describe makes code hard to reason about. You also touched on this when you say "THe code is hard to write; hard to read; hard to debug;...". I like immutable objects because they make code much easier to reason about, not just by myself, but observers who look on without knowing the entire project. Apr 30, 2018 at 11:58
  • 1
    @RahulAgarwal, "But why this problem becomes more prominent in context of functional programming". It doesn't. I think maybe I'm confused by what you are asking as the problem is far less prominent in FP as FP encourages immutability thus avoiding the issue.
    – David Arno
    Apr 30, 2018 at 12:41
  • 1
    How can your 13 second example become easier to analyze with immutable code? The variable y doesn't change but you can't even describe where the computation goes, making mutability more transparent and simpler. Which it's not, it's just this answer is receiving undeserved praise for an explanation that fails if you don't already agree with it.
    – djechlin
    Apr 30, 2018 at 17:17
  • 4
    Mutability creates an identity crisis. Your variable does not have a single identity anymore. Instead, its identity now depends on time. So symbolically, instead of a single x, we now have a family x_t. Any code using that variable will now has to worry about time as well, causing extra complexity mentioned in the answer.
    – Alex Vong
    Apr 30, 2018 at 21:15
8

what can go wrong in context of functional programming

The same things which can go wrong in non-functional programming: you can get unwanted, unexpected side effects, which is a well known cause of errors since the invention of scoped programming languages.

IMHO the only real difference on this between functional and non-functional programming is, in non-functional code you will typically expect side-effects, in functional programming, you won't.

Basically if it is bad, it is bad irrespective of OO or functional programming paradigm, right?

Sure - unwanted side effects are a category of bugs, regardless of the paradigm. The opposite is true as well - deliberately used side effects can help to deal with performance issues and are typically necessary for most real-world programs when it comes to I/O and dealing with external systems - also regardless of the paradigm.

4

I just answered a StackOverflow question that illustrates your question fairly well. The main problem with mutable data structures is their identity is only valid at one exact instant in time, so people tend to cram as much as they can into the small point in the code where they know the identity is constant. In this particular example, it's doing a lot of logging inside a for loop:

for (elem <- rows map (row => s3 map row)) {
  val elem_str = elem.map(_.toString)

  logger.info("verifying the S3 bucket passed from the ctrl table for each App")
  logger.info(s"Checking on App Code: ${elem head}")

  listS3Buckets(elem_str(1), elem_str(2)) match {

    case Some(allBktsInfo) =>
      logger.info(s"App: ${elem_str head} provided the bucket name as: ${elem_str(3)}")
      if (allBktsInfo.exists(x => x.getName == elem_str(3))) {
        logger.info(s"Provided S3 bucket: ${elem_str(3)} exists")
        println(s"s3 ${elem_str(3)} bucket exists")
      } else {
        logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
        logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
        excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
        println(s"s3 bucket ${elem_str(3)} doesn't exists")
    }

    case None =>
      logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
      logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
      excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
}

When you are accustomed to immutability, there is no fear of the data structure changing if you wait too long, so you can do tasks that are logically separate at your leisure, in a much more decoupled way:

val (exists, missing) = rows partition bucketExists
missing foreach {row =>
  logger.info(s"WARNING: Provided S3 bucket ${row("s3_primary_bkt_name")} doesn't exist")
  logger.info(s"WARNING: Dropping the App: ${row("app")} from backup schedule")
}
3

The advantage of using immutable objects is that if one receives a reference to an object with that will have a certain property when the receiver examines it, and needs to give some other code a reference to an object with that same property, one can simply pass along the reference to the object without regard for who else might have received the reference or what they might do to the object [since there's nothing anyone else can do to the object], or when the receiver might examine the object [since all of its properties will be the same regardless of when they are examined].

By contrast, code which needs to give someone a reference to a mutable object which will have a certain property when the receiver examines it (assuming the receiver itself doesn't change it) either needs to know that nothing other than the receiver will ever change that property, or else know when the receiver is going to access that property, and know that nothing is going to change that property until the last time the receiver will examine it.

I think it's most helpful, for programming in general (not just functional programming) to think of immutable objects as falling into three categories:

  1. Objects that can't won't allow anything to change them, even with a reference. Such objects, and references to them, behave as values, and can be freely shared.

  2. Objects that would allow themselves to be changed by code that has references to them, but whose references will never be exposed to any code that would actually change them. These objects encapsulate values, but they can only be shared with code that can be trusted not to change them or expose them to code that might do.

  3. Objects that will be changed. These objects are best viewed as containers, and references to them as identifiers.

A useful pattern is often to have an object create a container, populate it using code that can be trusted not to keep a reference afterward, and then have the only references that will ever exist anywhere in the universe be in code that will never modify the object once it's populated. While the container might be of a mutable type, it may be reasoned about(*) as though it were immutable, since nothing will ever in fact mutate it. If all references to the container are kept in immutable wrapper types that will never alter its contents, such wrappers may be passed around safely as though the data within them was held in immutable objects, since references to the wrappers may be freely shared and examined at any time.

(*) In multi-threaded code, it may be necessary to use "memory barriers" to ensure that before any thread can possibly see any reference to the wrapper, the effects of all actions on the container would be visible to that thread, but that's a special case mentioned here only for completeness.

2
  • thanks for impressive answer !! I think probably the source of my confusion is because am from c# background and am learning "writing functional style code in c#" which keeps on everywhere saying avoid mutable objects - but I think languages which embrace functional programming paradigm promotes (or enforce - not sure if enforce is correct to use) immutability. Apr 30, 2018 at 19:44
  • @RahulAgarwal: It's possible to have references to an object encapsulate a value whose meaning is unaffected by the existence of other references to the same object, have an identity which would associate them with other references to the same object, or neither. If real-word state changes, then either the value or identity of an object associated with that state can be constant, but not both--one will have to change. The $50,000 is which should do what.
    – supercat
    Apr 30, 2018 at 20:24
1

As has already been mentioned, the problem with mutable state is basically a subclass of the larger problem of side effects, where a function's return-type does not accurately describe what the function really does, because in this case, it also does state mutation. This problem has been addressed by some new research languages, such as F* (http://www.fstar-lang.org/tutorial/). This language creates an Effect System similar to the type system, where a function not only statically declares its type, but also its effects. This way, the callers of the function are aware that state mutation may occur when calling the function, and that effect is propagated to its callers.

1

I made a C# example: passing mutable values


   public class Program
    {
        public static int sum(List<int> list)
        {
            int sum = 0;
            foreach (var item in list)
            {
                sum = item + sum;

            }
            return sum;
        }

       public static int sumAbsolute(List<int> list)
        {

            for (int i = 0; i <list.Count; i++)
            {
                list[i] = Math.Abs(list[i]);
            }
            

            return sum(list); 
        }

        static void Main(string[] args)
        {
            var list = new List<int>() {
                -1,-2,-3
            };

            Console.WriteLine(sumAbsolute(list));
            Console.WriteLine(sum(list));
            Console.ReadKey();
        }
    }

So the outcome of both functions is now 6. And of course this is not correct

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.