"Many functions operating upon few abstractions" principle vs OOP
Asked Answered
C

4

16

The creator of the Clojure language claims that "open, and large, set of functions operate upon an open, and small, set of extensible abstractions is the key to algorithmic reuse and library interoperability". Obviously it contradicts the typical OOP approach where you create a lot of abstractions (classes) and a relatively small set of functions operating on them. Please suggest a book, a chapter in a book, an article, or your personal experience that elaborate on the topics:

  1. motivating examples of problems that appear in OOP and how using "many functions upon few abstractions" would address them
  2. how to effectively do MFUFA* design
  3. how to refactor OOP code towards MFUFA
  4. how OOP languages' syntax gets in the way of MFUFA

*MFUFA: "many functions upon few abstractions"

Cannice answered 12/5, 2012 at 15:41 Comment(3)
The Expression Problem seems to be what you're after.Contemptible
AN interesting video on the topic: infoq.com/presentations/Clojure-The-Art-of-AbstractionBloom
I can't find this quote in the articleWellhead
B
18

There are two main notions of "abstraction" in programming:

  1. parameterisation ("polymorphism", genericity).
  2. encapsulation (data hiding),

[Edit: These two are duals. The first is client-side abstraction, the second implementer-side abstraction (and in case you care about these things: in terms of formal logic or type theory, they correspond to universal and existential quantification, respectively).]

In OO, the class is the kitchen sink feature for achieving both kinds of abstraction.

Ad (1), for almost every "pattern" you need to define a custom class (or several). In functional programming on the other hand, you often have more lightweight and direct methods to achieve the same goals, in particular, functions and tuples. It is often pointed out that most of the "design patterns" from the GoF are redundant in FP, for example.

Ad (2), encapsulation is needed a little bit less often if you don't have mutable state lingering around everywhere that you need to keep in check. You still build ADTs in FP, but they tend to be simpler and more generic, and hence you need fewer of them.

Brandi answered 12/5, 2012 at 16:41 Comment(1)
That distinction is fundamental, and everybody should read this article by Scott Meyers about encapsulation.Tetter
S
9

When you write program in object-oriented style, you make emphasis on expressing domain area in terms of data types. And at first glance this looks like a good idea - if we work with users, why not to have a class User? And if users sell and buy cars, why not to have class Car? This way we can easily maintain data and control flow - it just reflects order of events in the real world. While this is quite convenient for domain objects, for many internal objects (i.e. objects that do not reflect anything from real world, but occur only in program logic) it is not so good. Maybe the best example is a number of collection types in Java. In Java (and many other OOP languages) there are both arrays, Lists. In JDBC there's ResultSet which is also kind of collection, but doesn't implement Collection interface. For input you will often use InputStream that provides interface for sequential access to the data - just like linked list! However it doesn't implement any kind of collection interface as well. Thus, if your code works with database and uses ResultSet it will be harder to refactor it for text files and InputStream.

MFUFA principle teaches us to pay less attention to type definition and more to common abstractions. For this reason Clojure introduces single abstraction for all mentioned types - sequence. Any iterable is automatically coerced to sequence, streams are just lazy lists and result set may be transformed to one of previous types easily.

Another example is using PersistentMap interface for structs and records. With such common interfaces it becomes very easy to create resusable subroutines and do not spend lots of time to refactoring.

To summarize and answer your questions:

  1. One simple example of an issue that appears in OOP frequently: reading data from many different sources (e.g. DB, file, network, etc.) and processing it in the same way.
  2. To make good MFUFA design try to make abstractions as common as possible and avoid ad-hoc implementations. E.g. avoid types a-la UserList - List<User> is good enough in most cases.
  3. Follow suggestions from point 2. In addition, try to add as much interfaces to your data types (classes) as it possible. For example, if you really need to have UserList (e.g. when it should have a lot of additional functionality), add both List and Iterable interfaces to its definition.
  4. OOP (at least in Java and C#) is not very well suited for this principle, because they try to encapsulate the whole object's behavior during initial design, so it becomes hard add more functions to them. In most cases you can extend class in question and put methods you need into new object, but 1) if somebody else implements their own derived class, it will not be compatible with yours; 2) sometimes classes are final or all fields are made private, so derived classes don't have access to them (e.g. to add new functions to class String one should implement additional classStringUtils). Nevertheless, rules I described above make it much easier to use MFUFA in OOP-code. And best example here is Clojure itself, which is gracefully implemented in OO-style but still follows MFUFA principle.

UPD. I remember another description of difference between object oriented and functional styles, that maybe summarizes better all I said above: designing program in OO style is thinking in terms of data types (nouns), while designing in functional style is thinking in terms of operations (verbs). You may forget that some nouns are similar (e.g. forget about inheritance), but you should always remember that many verbs in practice do the same thing (e.g. have same or similar interfaces).

Spouse answered 12/5, 2012 at 21:13 Comment(3)
+1 I would argue that a proper use of OO involves focusing on the verbs also - at its core OO is really a way of defining polymorphic functions on particular bundles of data.Explode
I think it's the other way round: OO is about verbs (imperative methods, invoked to "do" stuff) while FP is about nouns (declarative functions, denoting something). For example, in OO you often have methods of the form x.getSomething(), instructing to "get the something from x". In FP you instead have a function something(x), denoting "the something of x".Brandi
@Marcin,@AndreasRossberg: I agree with both of you. Moreover, GoF says that objects should be defined in terms of operations they may perform and not of their internal structure (just like interfaces). However, in this particular case where we compare FP and OOP I would say that in OOP you should first think of entities you have in domain area (users, cars, contracts) while in FP you should first think of actions performed in the domain (sell, buy). Sometimes it leads to different designs. E.g. in FP you may end up with 2 modules for selling and buying and represent all entities as hash maps.Spouse
I
6

A much earlier version of the quote:

"The simple structure and natural applicability of lists are reflected in functions that are amazingly nonidiosyncratic. In Pascal the plethora of declarable data structures induces a specialization within functions that inhibits and penalizes casual cooperation. It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures."

...comes from the foreword to the famous SICP book. I believe this book has a lot of applicable material on this topic.

Ira answered 12/5, 2012 at 18:17 Comment(0)
E
0

I think you're not getting that there's a difference between libraries and programmes.

OO libraries which work well usually generate a small number of abstractions, which programmes use to build the abstractions for their domain. Larger OO libraries (and programmes) use inheritance to create different versions of methods and introduce new methods.

So, yes, the same principle applies to OO libraries.

Explode answered 12/5, 2012 at 15:51 Comment(6)
Disagreed, the difference is real in client code as well. Where I model all my data in Clojure using nothing but maps and seqs, in Java I'm forced to constantly invent new typesafe containers for each use case. One should note that the primary difference is not between OOP and FP, but between static and dynamic typing.Scribble
Marko, 1. I agree with you that the difference is in client code as well. 2. I don't agree it's about static vs dynamic typing. Currently I'm programming in Ruby. Comparing to Java the code became 3x times smaller, but the large-scale structure is the same: we have a lot of classes with some methods coupled to those classes. Very little reuse. I don't like thatCannice
@Cannice Yes, as I was thinking about my words and adding Ruby/Rails into consideration, I got to the same conclusions. Ruby may not force you into using classes, but it's definitely pushing you in that direction and away from a clean separation of data structures and code. In Clojure it's the other way round -- you still have classes (well, records or whatever), but the first choice is always map/vector/seq.Scribble
@MarkoTopolnik That's because you want statically safe containers in java, which has a quite poor type system. It doesn't really speak to the number of abstractions (you are implementing the same kind of abstraction over and over).Explode
@Cannice Just because you achieve very little re-use, it does not follow that re-use is not possible, or even that it is especially difficult.Explode
No, I specifically don't want statically safe containers, but if I don't have them, then my code is an even larger mess due to all the downcasts. In Java you need the types just to be able to call functions. Have you ever tried writing Java code with Map<Object,Object>? And the point about the number of abstractions really goes to the number of times you write that abstraction, not to the number of distinct abstractions at some level of description.Scribble

© 2022 - 2024 — McMap. All rights reserved.