3
$\begingroup$

I would like to understand more about the 'orthogonality' of OOP and functional programming.

What makes me confused is the 'encapsulation' of OOP and 'referential transparency of functional programming. We do often put mutable state(information) as private member variables of a class and have a public method to manipulate them. This clearly goes against the side-effect avoiding of functional programming.

It seems like this kind of problem stands for most of the common OOP classes. I do know that class and inheritance are used to make 'subtypes' in functional programing, which don't have methods but are more like data collection.

I guess maybe the Numpy style methods, which does not modify itself but returns a new instance with intended modification could be a solution.

But still not sure that one could see the implicit argument of method like self as part of input in concerns of 'referential transparency' (f(a) == f(b), if a == b), and stands with 'pure function' concept if the self is immutable but returning a new instance.

$\endgroup$
4
$\begingroup$

It is true that encapsulation in OO is typically used to hide private mutable state. However, this is a property of traditional OOP, which was an evolution of procedural programming. It is not a property of encapsulation per se, which can be (and is) used in a functional setting just fine.

In fact, even objects can be functional. For example, here is a functional counter class (pseudo code):

class Counter(init : Int) {
  private val c : Int = init
  public get() : Int { return c }
  public inc() : Counter { return new Counter(c+1) }
}

The inc method does not mutate the state, but returns a new object with adjusted attribute. Such functional objects are useful in some scenarios, and do maintain referential transparency:

(new Counter(0)).inc() == (new Counter(0)).inc()

The more common approach in FP, however, is encapsulation through abstract data types (ADTs), not objects. This typically involves modules. For example (again pseudo code):

module Counter {
  public type Counter = private Int
  public new(init : Int) = init
  public get(c : Counter) : Int = c
  public inc(c : Counter) : Counter = c+1
}

Here, the type Counter exported by the module is public, but its definition is not, so that client code can only access values of this type through the module's public interface. Again, this maintains referential transparency:

Counter.inc(Counter.new(0)) == Counter.inc(Counter.new(0))

And to drive the point home that encapsulation and state are orthogonal concepts that can be used in any combination, the following example shows that ADTs and modular encapsulation can also be used to hide state:

module MCounter {
  public type MCounter = private Ref(Int)
  public new(init : Int) = ref(init)
  public get(c : MCounter) : Int = !c
  public inc(c : MCounter) = (c := !c+1)
}

Here, Ref(Int) is a reference of type Int, i.e., a mutable cell that can be created with ref, read with !, and written with := (which is how impure functional languages like ML incorporate state). The inc function mutates its argument in place.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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