简体   繁体   中英

Objects in functional programming - Immutability

Recently I have been learning to program in Erlang as a long time C and C# developer. I am fairly new to functional programming. Now, I am trying to understand how do objects in languages like Scala work. I have been taught that OOP is all about changing state of given object using its public methods. Those methods change state of public properties and private members. But now I hear that in functional programming all objects should be immutable. Well, I agree that once assigned variable (in given function) should remain pointing to the same object. But does this "immutability" mean that I cannot change the internals (properties, private members) of given objects using their public methods? This makes objects just like simple data containers. It extracts all of the functionality outside of them. This makes objects act more like structures in C. It is something that is strange to me. Maybe I am missing something? Is it possible to use objects the old-fashioned-way and still consider it as functional programming?

You are mixing up three different concepts. Functional programming, mutability, and OOP are three different things.

I have been taught that OOP is all about changing state of given object using its public methods.

Yes and no. The important thing in O OP is that you have objects , which can simultaneously carry around data and code (their member methods), and that you talk to the objects using their interface , so that the object can then

  • dispatch to the concrete implementation of the called method
  • supply additional information that is stored in the data that the object carries (accessing this in the method implementation)
  • execute the code of the method implementation

Nobody prescribes you what this method call does, or that it must modify some state.

It happens to be that OOP helps to restore some basic sanity when working with state. This is because the horrifyingly complex global state of the application can be cut up into smaller pieces and hidden inside mutable objects. Moreover, those mutable objects can additionally attempt to maintain at least some local invariants, by prohibiting direct access to their state, and providing only a restricted set of operations that can modify that state.


But now I hear that in functional programming all objects should be immutable.

They should respect referential transparency. If your objects have no mutable state and only methods without any side effects, then it is sufficient for referential transparency. Sufficient, but not necessary : the objects can have more complex inner structure, but appear completely immutable from the outside. Caching is a good example.

Furthermore, even pure functional programs are not restricted to working with immutable data structures only. There is such thing as pure mutable state . The idea is that your functional programs are used to construct something like complex action plans for dealing with mutable state. The plan itself is an immutable entity. This plan can be very complex, but thanks to the fact that it is built from pure functions, it can be still sufficiently easy to reason about. This plan, which is built using only pure functions, can then be given to a small and simple interpreter, which executes the plan on mutable memory. In this way, you reap the benefits of both worlds: you have conceptual simplicity of pure functions while you are building the plan, but you also have the performance of close-to-the-metal computations when you execute this plan on mutable data structures.


But does this "immutability" mean that I cannot change the internals (properties, private members) of given objects using their public methods?

In general, yes. However, in Scala, immutability is not enforced by default. You can decide what parts of your application are complicated enough to reason about, so that it might be worth it to restrict yourself to pure functions. Everything else can be implemented using ordinary mutable structures, if this is easier.

This makes objects just like simple data containers. It extracts all of the functionality outside of them.

No, because objects still carry their virtual dispatch table with them. You can then ask the object from the outside to invoke the method apply(integer i) , and the object, depending on what kind of object it is, might then invoke completely different things, like

/** get i-th character */ 
String.apply(integer i) 

or

/** get value for key `i` */ 
TreeMap.apply(integer i)

You cannot do this with C structs without essentially re-implementing subclass polymorphism as a design pattern.


Is it possible to use objects the old-fashioned-way and still consider it as functional programming?

It's not an all-or-nothing game. You can start with a classical oop-language (with mutable state and all that), which supports the functional programming paradigm to some degree. You can then look at your application, and isolate those aspects of it that require more precise control over the side effects. You have to decide which side effects matter, and which are less critical. Then you can express the really critical parts using the somewhat stricter approach with pure functions (pure in the sense: as pure as you need them to be, ie not performing any critical side-effects without declaring it explicitly in their signature). The result would be a mix of classical OOP and FP in one application.

Writing programs using pure functions can be thought of as a construction of a proof in a somewhat primitive logic. It's nice if you can do it when you need it (using a stricter approach with pure functions), but it can also be nice if you can omit it when you don't need it (using the usual approach with messy side-effecty functions).

OOP is all about changing state of given object using its public methods.

It's a lie!

in functional programming all objects should be immutable

It's a lie!

But does this "immutability" mean that I cannot change the internals (properties, private members) of given objects using their public methods?

There are strict immutability and "simulated" immutability. With "simulated" immutability you can change internals, but you shouldn't produce visible changes. For example, caching of heavy computations is still acceptable.

Is it possible to use objects the old-fashioned-way and still consider it as functional programming?

It depends on how exactly you are mutating objects and how you define FP. Actually, god damn it, you can mutate objects in FP.

Maybe I am missing something?

Yes, there a lot of things you should learn.


There are several things you don't understand about immutable objects:

  1. Immutable object still can has computed properties. So it's more than structure in C. See Uniform access principle for details. Note: in C# structure can have computed properties
  2. Immutable object still can have useful methods. So it's more than structure in C. You can have some logic in immutable object, but this logic can't mutate state. Note: in C# structure can have computed properties.
  3. Also, there is a huge difference between objects and structures in memory-safe languages (like C#, Scala, Erlang): you can't work easily with structures. Generally, there are no pointers in safe code (except things like unsafe regions). So you can't just share same structure between different structures.
  4. Immutable object still can encapsulate some data. Encapsulation is the key to make abstractions (either good or bad)

Here are things that you should know about OOP:

  1. OOP doesn't requires to mutate state. OOP without mutations is still OOP.
  2. OOP has nice useful thing: inheritance. There are no such thing in pure FP.

Here are things that you should know about FP:

  1. FP doesn't requires you to use immutable data. But it's much easier to work with immutable data in FP.

  2. 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?"

    Good FP languages have a lot of ways to work with mutable data to meet performance needs. FP just loves to hide mutations.

  3. Only pure-FP languages are forcing you to use immutable data.

  4. FP has get it's name for functions as first-class citizens. See more on wiki . You can use & pass functions just like you use & pass objects.

  5. Well, a lot of FP is not about functions.


You should also know how OOP and FP are related:

  1. Encapsulation and polymorphism works differently in OOP and FP. It's very clear how encapsulation work in OOP: private/protected and another access modifiers. But in pure FP only way to encapsulate some data is to use closures.
  2. Polymorphism works differently in OOP and FP. In OOP you are using subtype polymorphism. But in pure FP you should use thing like protocols (interfaces) or multimethods.
  3. “Closures are poor man's objects and vice versa” . You can express same code with closures and with objects.
  4. C# and Scala are multi-paradigm languages. They both supports OOP and FP. It's not bad to mix OOP and FP. Actually it's best way to produce readable code.

And now, I want to explain why there a lot of people whom thinks that some paradigms can't be combined:

  1. OOP was born before immutability become useful. In old days programs was simpler & we worried a lot about memory.
  2. OOP was born before our CPU become able to parallel code. So, immutability (which makes writing parallel code easier) wasn't helpful.
  3. Before recent popularity of FP it was useful primarily only for academists. Academists aren't in love with uncontrolled mutations.
  4. Potentially FP program can be reorganized in such crazy ways that we can't depend on time order of side-effects (and mutations as part of side-effects). No time ordering = useless mutations. But languages like C#, Scala aren't so much crazy. But see next point.
  5. FP has lazy computations at nearly birth. Also they have monads or similar mechanism to replace direct control flow. And function can be executed at any moment, because it's can be stored anywhere. And this make side-effects (and mutations) a bit harder for controlling.

Just to make things clear: creators of FP-style aren't idiots. They want side-effects to be explicit & controlled. So there is a nice concept: pure function .


Summary :

  • OOP is oftenly used with mutations. But it's not required. OOP is focused on objects.
  • FP is oftenly used with immutability. But it's not required. FP is focused on functions.
  • You can mix OOP, FP, mutations and immutability - all of them.

But does this "immutability" mean that I cannot change the internals (properties, private members) of given objects using their public methods?

Yes.

This makes objects just like simple data containers. It extracts all of the functionality outside of them.

No. Typically much of the functionality of immutable classes is provided by their constructors. A constructor is not neccessarily just assigning values passed to it as arguments to member variables. Another part of the functionality can be found in getter-like methods that not only return the values of member variables but perform more complex transformations on them.

This makes objects act more like structures in C.

No. Structs in C are completely mutable. Anyone can access and modify their elements.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM