There is a fundamental point here that really bears thinking about.
Software design has as a fundamental problem being able to express yourself in a way that is clear and remains clear as things change. Basic principles of good software design, like reducing cohesion, remain good principles no matter what "paradigm" you think you're using. When you change paradigms, be aware that the basic principles remain the same no matter what label you give them. And since design is all about tradeoffs, if you are going to have to violate a principle, you might as well do it in the clearest and most straightforward way possible.
Let me give an example. A singleton is bad for all of the reasons that a global variable is bad. If you need one, I prefer to make it a global variable simply because that is honest about what design principle has been broken. (Unless I want to make it lazy - then it is easier to write with a singleton!)
So learn the principles. Figure out what they look like and are called in your paradigm. Get on with life and solve real problems.
Let me give a concrete example. I learned more about good OO design from the first edition of Code Complete than any other book that I ever read. Even though it was entirely about procedural programming. Because the principles that it discussed, from how to name functions to the value of abstract data types to information hiding, all apply directly to OO. And to functional programming. And to aspect oriented programming. They are truly universal. Learn the principles, they are more important than the labels.
Singletons can be made thread-safe (with difficulty), and you can use complex lazy singletons in C++ code while complex globals gets you into trouble with module initialization order. So there're some very pragmatic reasons to use singletons over globals, even if the same drawbacks apply to both.
As someone self-teaching their way into software engineering, would you be able to suggest any resources aside from Code Complete for these principles?
I really like Hadi's criticism of OO, and in fact I would like to hijack it and take it a few steps further, to paint what in my mind is a more realistic picture. Functional programming is only an alternative choice of paradigm. Expert programmers will use the best tool for the job and eschew the pointless search for the absolute best.
Why OO? Classes exist to decouple behaviors from data. In fact, their purpose is to think in terms of cohesive sets of behaviors instead of data-driven functions.
Does this apply to any possible case? No, it's a leaky abstraction.
Why FP? Functions exist to decouple behavior from other behavior. In fact, their purpose is to think in terms of completely decoupled sets of behaviors instead of cohesive functions.
Does this apply to any possible case? No, it's a leaky abstraction.
Why AOP? Aspects exist to decouple behaviors that apply across classes. In fact, their purpose is to think in terms of orthogonal sets of behaviors instead of hierarchical sets of behaviors.
Does this apply to any possible case? No, it's a leaky abstraction.
Why Procedural Programming? To separate the functionality of the application in separate behaviors. In fact, the purpose of procedures is to think in terms of behaviors instead of sequences of operations.
Does this apply to any possible case? No, it's a leaky abstraction.
Why Declarative Programming? To express solutions in terms of goals instead of behaviors. In fact, its purpose is to think in terms of desired state, not sequences or compositions of behaviors.
Does this apply to any possible case? No, it's a leaky abstraction.
Why Logic Programming? To explore the logical consequences of given facts, instead of focusing on behaviors or desired outcomes. In fact, its purpose is to find new truths starting from given truths.
Does this apply to any possible case? No, it's a leaky abstraction.
I will pick a minor nit: classes, as we know them from C++ and Java, are about coupling data with behaviour, not decoupling. You bind a specific data representation to an interface for working with that representation opaquely, in the hope that no one inadvertently depends on your innards. Most other languages do it with modules.
Encapsulation makes sure the data is hidden away, and, in theory, is only accessible through methods, which may, or may not internally use the same data.
For example this pseudoclass
public class Sum1
{
private int _a, _b;
public Sum1(int a, b)
{
_a = a;
_b = b;
}
public int ReturnResult()
{
return _a+_b;
}
}
And this other pseudoclass
public class Sum2
{
private int _tot;
public Sum2(int a, b)
{
_tot = a+b;
}
public int ReturnResult()
{
return _tot;
}
}
Have the same behavior/functionality but depend on different internal data - in this sense the data has been decoupled from the functionality: one can treat the class as a black box - the coupling happens in the implementation of the class, but not anymore in the design of it.
One would argue that both are flawed because they are, in essence, a function which is not espressing itself clearly and cleanly. You are co-mingling state with computation in a way that obscures and loses the advantages therein.
If you want to make an example of something, then use a Reader, such that you are abstracting away the "source" of what is read and the side-effects thereof. Or an expiring cache (which is a good example of mutable state.) Or a mixed Reader which "reads" from either a cache or in the case of passing the expiration mark on the "key," some unknown "source."
To all the guys saying the example is bad code or is not complex enough. Well, of course! Think of this as a doodle to present a certain example. Quit complaining that it isn't to scale.
He just means to show that OO lets us have the luxury of just caring about the functions we need to use to manipulate/use/access the data, without any regard to how it is internally stored.
Encapsulation may hide the data away but it does not hide the effects of state. If an object contains mutable state, it is not referentially transparent. This makes the abstraction leaky, as users need to understand the possible states of an object lest it behave unpredictably.
> If an object contains mutable state, it is not referentially transparent.
Why shouldn't an object contain mutable state? When solving a problem for which pure, stateless programming is not suitable, where would you put state if not inside an object?
In an STM monad, or an ST monad, or even an IORef, or a core.async channel, or whatever. Functional languages have manifold approaches to state that do not require you to lose track of time. And that's the real key to understanding state, isn't it? Time.
Please do not drag this discussion to stateful vs. stateless programming. It's about OO, which is typically, in practice and in theory, associated with mutable state.
Behavior does not necessarily depend only on state. Behavior depends on state and the code that uses state to implement said behavior. Encapsulation allows us to think on terms of behaviors and not of internal states.
That said, this paradigm is easily broken by bad code.
The code does not demonstrate encapsulation because the data type has not been decoupled from the implementation. A developer cannot change the internal representation to a float without changing code that calls ReturnResult, thereby defeating encapsulation. (Java cannot have two methods of the same name that differ by return type alone.)
Encapsulation means that objects should perform operations on their data, instead of giving their data to other objects. See Tell, Don't Ask (http://pragprog.com/articles/tell-dont-ask) for details. Encapsulation also lends itself to the Open-Closed Principle whereby class behaviours can be changed through inheritance alone.
Please see (and critique) my self-encapsulation presentation for details:
> The code does not demonstrate encapsulation because the data type has not been decoupled from the implementation.
Not all code has to be applicable to more than one use case. What you're suggesting is premature generalization, which is just as bad as premature optimization. One might argue that p.g. is a kind of p.o. with the metric to be optimized being abstraction.
> Why OO? Classes exist to decouple behaviors from data.
This seems like the opposite of how it generally works. Classes tie behaviors to data. This is what people generally seem to mean when they talk about encapsulation in OO — the behavior and the data are bound together very well.
If you have a datetime object you can stop thinking about the fact that it's internally implemented with e.g. ticks since the epoch. In fact, you don't care anymore what data it depends on, because you only depend on behaviors.
I can see encapsulation being useful in that case, though I'm of the opinion the use-cases for encapsulation don't outweigh the disadvantages it brings.
They work together, by encapsulating the data internally, one is free to change its representation. It keeps the coupling to that data local, the objects methods.
The problem with this argument is that certain abstractions are more useful than others, particularly in certain problem domains. You rarely need a language with manual memory management when designing a web application, for instance.
I'm saying it's not a useful comparison. Some tools are more capable generally, and applicable to more problem domains.
For instance, I'm sure there must exist problems that require inheritance, polymorphism, encapsulation and implicit state mutation, but those are such weirdly specific requirements that I don't think OOP is applicable in most of the cases where it's in use.
Let's say you want to store some data. Mostly in your final application you'll be writing to a database, but you'd like to keep the option of writing to different types of storage (files on disk, amazon S3, memory for testing).
Polymorphism lets your database storage object present the same interface as your file storage object. Pretty useful if you want to be able to substitute them!
Encapsulation lets you make sure at compile-time that you haven't accessed anything specific to a particular implementation. If someone else maintains that part of the system, it also lets them mark interface parts that won't change between versions and some implementation parts that may change between versions.
Implicit state mutation lets you open the file (or create the file or establish the database connection pool) on the first attempt to access, letting multiple implementations present the same interface even when different things are going on in the background.
Inheritance is useful if you have multiple objects - maybe you have one for reading inputs, one for storing work-in-progress, one for writing outputs - and you want to reduce code duplication by sharing implementation details. (IMHO this is one of the less useful ones)
That's just polymorphism. You don't need to encapsulate the connection data, you don't need to cache implicitly (what's wrong with an explicit memoize?), and inheritance isn't necessary for sharing implementation details.
To be more specific in my criticisms, it's not that polymorphism or event inheritance aren't useful tools. It's more that OOP inextricably ties these concepts together, making it hard to reason about them separately.
Your comment demonstrates this, I think. All the functionality you describe would be implemented in a functional language with polymorphism and higher-order functions, but because an OOP language binds these concepts so tightly together, it's hard to distinguish between them when thinking about a OO design.
If not for encapsulation, how do you denote those parts of the interface which you commit to keeping stable and those parts of the interface that are for internal use only?
Marking some functions as suitable for internal use is fine. That's not something that's generally considered as falling under the definition of encapsulation.
Encapsulation is a mechanism to hide information, to conceal internal state or values. I'd argue that this an anti-pattern. Hiding data is almost always the wrong thing to do.
Encapsulation is advisable because it allows controlling the scope of variables. It's not the wrong thing to do when you keep state. It's the right thing to do.
encapsulation allows changing the internal representation without having to revisit every use case external to the class. Typically that means adding new states, or changing between a collection of Booleans to/from a state machine.
Right, but consider it in terms of what you gain, and what you lose.
In terms of what you gain, encapsulation might save time, as in some circumstances you can keep the same API without changing the underlying representation of data.
But the price you pay is that you need to reimplement some subset of the functionality a data structure already has, every time you write a new class. You might need to write accessors, iteration, transversal, searches, serialisation, merging, and a whole bunch of other things you get for free with a data structure. Encapsulation means continually re-implementing the wheel, which takes time.
Another huge problem with encapsulation is that you lose transparency and constraints. If I have a system represented fully or in part by a data structure, it gives me visibility and certain guarantees. For example, if I set a key in a map to a value, I know that when I read that key I'll get the same value out. You have no such guarantees with objects.
Encapsulation trades possible gains for certain losses. This might be acceptable, if you had some reason to believe you stood to gain a lot more than you'd lose. But there's no empirical evidence to suggest that there are any significant gains in practice from encapsulation. However you look at it, it seems a really bad trade.
Purely anecdotally, my own experience is that idiomatic OOP design requires an order of magnitude more refactoring than data-driven design.
Refactoring is a fact of life. Encapsulation makes it trivial to identify all places that need to be addressed. That is a certain gain.
Talking about 'certain this' and 'possible that' is subjective. I'd have labeled those the opposite way, for instance.
If you value transparency (your 'data structure' is actually an object itself e.g. a hash map) then inheirit. Its not rocket science. One solution doesn't have to fit everything.
Refactoring is a symptom of screwing up the architecture of your application. Excessive refactoring is caused by excessively screwing up. I'd argue that if refactoring seems like a fact of life with OOP, that might indicate there's something very wrong with that paradigm.
> Encapsulation makes it trivial to identify all places that need to be addressed.
If encapsulation was all you needed, there wouldn't be so many refactoring tools for OOP languages that rename and move methods and classes around.
> Talking about 'certain this' and 'possible that' is subjective.
I'm not being subjective. In virtually every OOP application there will be an object that implements at least part of the functionality of a map. At the bare minimum, there will be at least one object that has a getter or a setter method.
>If you value transparency then inheirit.
Inheritance doesn't provide transparency in any meaningful way. The private members of the class are still private. The getters and setters are still opaque.
>your 'data structure' is actually an object itself e.g. a hash map
Data structures are only objects in object-orientated languages. In other systems, they're either primitives or constructed from primitives. At the lowest level, you can construct any data structure you like from an ordered array of bytes.
Refactoring is a result of having time to optimize your product because its undergoing pressure you didn't (couldn't) predict. Its reimplementing something you did cheaply at first because performance wasn't as important as time-to-market.
Its a fantasy that code can somehow be built to last forever.
Sure, there may be times when you may have very good reasons for screwing up the architecture of your application, and there will be times when you need to restructure existing code. But this should be the exception, not the norm.
I'd argue that OOP, particularly OOP involving mutable state, exacerbates the problem immensely. It's extremely difficult to build robust systems in languages that have few constraints and little emphasis on isolating functionality.
I am guessing you have a JS background (i may be wrong. You may be thrice my experience and capability in languages of my choice). Either way, IMO those requirements aren't that weird. When you have VERY VERY huge software, this stuff comes in very handy. I love different, non-OO languages for the magic they bring to the table, but that doesn't mean i'd discredit OO languages.
I agree that some paradigms are more useful then others. Its rare to use declarative and logic based languages. (i assume declrative here means where u declare a goal without regard to how to achieve the goal). But OO is still pretty much needed in a ton of cases. So i wouldn't bunch it into the "less used" category. Heck web development benefits greatly from this stuff too! My current webproject is in JSP! yay java!
I tend to use Clojure on a day-to-day basis. The functionality that OOP groups into classes is separated out into individual tools in Clojure, so you have a tool for inheritance, a tool for polymorphism, and so forth.
When you use the tools of OOP separately, you start to appreciate how little you actually need them. A program in a functional programming language will use very little mutation. Polymorphism is a little more common, but not by much. Inheritance is practically never useful - I've only seen one case where it's ever been used outside a OOP language. I have yet to find a case where I'd need all three tools simultaneously.
Encapsulation is also something that seems to have far more disadvantages than advantages. It sounds like a nice idea, hiding your data structures behind an API that doesn't need to change, except in practice it doesn't save much refactoring, and you lose all the benefits with working with data.
The problem with this argument is that certain abstractions are more useful than others
Well,
I think one should say the thing to keep in mind with this argument is...
And "certain abstractions are more useful than others" should be certain abstractions should be used more often than others ... but you also still need to know how and when to use ... the less commonly needed ones, 'cause if you don't, they can still bite you.
I'd like to tie this to languages and their age.
I feel like young languages have a tendency to advocate only one of those, and as they mature they engulf most or all of them.
C# used to be all about OO, then it got FP (lambdas), and dynamism..
The extreme example could be Lisp, in which you can do pretty much any of the paradigms you mentioned.
By tying them together? That isn't what decouple means.
I have to ask, have you ever tried any sort of declarative programming? The fact that you list it, and the two main branches of it separately makes me think you haven't. You should really try it sometime, you might re-think the vague dismissal of all programming paradigms as equals.
I think that the article uses a bad example. OO is actually really bad in db apps (DTO apps). OO shines when you are in one big continuous memory space, and don't have to worry about the data behind the objects. I see OO as a good way of abstracting the data away from the logic of your program.
This abstraction works well with classical desktop apps (lots of code, one big heap, pointers are always valid), but breaks down badly when your program is about communicating data and you start to cross memory address barriers.
A lot of modern apps are about data. You see a lot of networked apps, db apps and layered apps with different technologies in every layer, communicating with each other only through shared data formats. Such an app is primarily concerned with shuffling data around, and oo breaks down into a horrible mess. Each transition to a data representation requires a complete serialization of the object graph to a reasonable data representation and back again at the other side. This means that you essentially have twice the amount of work than if you would operate on the data directly as you would do with FP.
Once the amount of "internal juggling" with the data exceeds a certain limit, OO might be beneficial. But for data-centric apps, forget it.
This is a really interesting point. It does seem like a lot of OOP/patterns books seem to talk about desktop apps that are quite different to your typical web app, which could be characterised (perhaps uncharitably) as CRUD with a bit of extra stuff glued on.
In fact, most of my experience with large codebases has been in web development, in which you're basically providing a nice front end to a database.
If databases are just structured data, then it seems a bit redundant to wrap that in an object-metaphor (e.g., with an ORM) and then use even more objects to perform operations on the data by way of those ORM-objects.
Django is a good example of the kind of thing I'm talking about. I've been thinking a fair bit lately about the limitations of this style of framework, and I wonder if this idea of functions operating on structured data might be a better way to approach things.
It sounds like Scala might be a good language to experiment with this kind of approach, as others have suggested elsewhere in this thread.
"Bad programmers worry about the code. Good programmers worry about data structures and their relationships." -- Linus
If your data structures don't exist outside of your functional code acting on them, it's hard to reason about the data structures themselves in a vacuum.
To my mind, it's an argument for explicit definition of data structures (from me, not Linus).
It seems to me that structs and/or objects do that pretty well. I'm not sure what you're talking about with 'opaque object structure'. class MyClass { member1, member2 } seems pretty transparent to me.
Anyways, I was just explaining why many people prefer to have struct/object definitions separated from the code that operates on them. YMMV, you don't have to agree.
If you're just treating a object as a map of keys to values, then it can be fairly transparent, but unless you're using a language like Javascript, where maps and objects are synonymous, objects are much more difficult to work with. It's hard to transverse them, or to merge them, or often even to look up a list of keys.
I'd suggest that a better solution would be to just use maps, at least in languages with good support for raw data structures.
"It's hard to transverse them, or to merge them, or often even to look up a list of keys."
That's exactly what I'm arguing against in my comment on making your important structs/objects and their relationships explicit. Randomly mutating them together all over your program might seem more flexible but at a cost of making you worry about your code rather than your data definitions. And that's without even getting into performance implications.
If you actually need a Map, of course, that's a different situation than the ones I'm thinking.
Using a map doesn't necessarily mean random mutation, or giving up on your type system. In languages with immutable data structures and a sophisticated type system, you can type-check the keys and values in a map.
Late answer, but nevertheless... I don't particularly think that it's a good idea, but I believe that the original intention was to build "living" data.
So you would look at your object structure and could treat it as data, but internally some objects could be proxies to backend system and fetch data on-demand. Some data fields might also be calculated from other fields. This is the idea which Java promotes by having the JavaBean standard. You never know if your data element is backed by actual data or is the result of a computation.
This type of abstraction can simplify certain problems by giving the illusion of a simple data structure, whereas a lot of computations are taking place behind the scene when the data is traversed.
This abstraction is used in may ORM frameworks for instance. You can follow relations between entities and these will be lazily loaded behind the scene as you move around in the object graph.
The problem is that such a living object can only live in its own environment. For ORMs, you cannot move outside the current DB transaction, or else the objects "die". To see some of these problems in the Java world, google "LazyInitializationException".
Pure data, on the other hand, is "dead by definition" so to speak. It does not contain any own logic and can be moved between environments without any problem. The backside of pure data is the very fact that it is dead, that is that you often have to accompany it with logic to interpret it.
With live objects, the interpreting logic is intertwined with the data. This is both the advantage and flaw of OO.
With OO, you have data and logic close together, and have to pay the price if you try to pry them apart.
With FP, you have the data separately, and have to cope with the mental load of knowing which logic you need to apply where in your data structures in order to interpret them.
If you think of classes as namespaces and not objects (which is what they are half the time), it becomes a bit saner. Pragmatism makes the world work.
You can still write functional code in OO. Heck if it makes you feel better, you can just make a macro to rename 'namespace' to 'class' and then you can write code like
namespace Utils {
function thingy() {...}
}
Namespaces are generally an improvement over globally defined functions as it lets you mix and match libraries without having to worry that you included the right header files, etc. Again, pragmatism.
Agreed. I think the problem with OOP (at least in most common implementations) is that objects (and/or classes) just do too much. They serve as complex data types, records, code modules, contracts, callback containers, etc. It's hard to grasp disparate concepts when you keep calling them the same name. Even harder to construct and explain more advanced stuff layered on top, such as various design patterns that actually make those objects useful.
You can make good arguments in either direction - either having a single generic type (a class) that fits all of these concepts, or having specific types for each of those concepts.
Having just a generic type gives the language some useful simplicity as the single 'class' container can fulfill all of those needs yet not need the user to remember all the caveats of each specific type as the 'class' concept is generic enough to fit to almost anything.
Having specific types is very good for teaching the different concepts at a higher level as well as documenting what is intended for future readers of the code (as long as they also understand all of the concepts involved). Convention over configuration, really.
I personally prefer the more generic types as they don't try to lock you in to the authors specific biases and requirements - that's what frameworks are for.
Agree.
I find it quite interesting how the concept of classes resp. objects has been adapted and moved away from what they really were let's say in Smalltalk (message processing entities) ... and I think this "progress" was not for better regarding the clarity of the concept.
I think this progress has to do with "type systems" and additionally with the very powerful tools c++ f.e. added to the tool box. The more powerful the toolbox the harder it is to truly grasp it, it seams which steepens the learning curve considerably unless only the very rudimentary "features" of oop are used, as it happens mostly in commercial 9-5 software development.
Have you programmed in Smalltalk? Classes tend to do a lot more (well everything that can be done) compared to C++. Smalltalk has a clean design for the core parts so these hang together pretty well. Nonetheless, inheritance hierarchies are deep and there are many fat classes. So small size in itself is usually not a virtue, clarity in role is on the other hand.
Just about any OO system can do this using interfaces, abstract base classes, mixins, traits etc. Yes it is a good way to approach design rather than building unique behavior from scratch over and over. But you don't need to go anywhere near PERL to accomplish this.
I think you're conflating languages that strongly support OO with OO itself. OO is a paradigm, a way of organizing your program. Using classes (meaning, as a language primitive) as namespaces and not as, well, classes is specifically avoiding OO programming. That's fine and, like you said, perfectly pragmatic, but it's not writing "functional code in OO". It's just repurposing language primitives that were designed for OO .
I agree with your specific response here, but I think it's worth pointing out that it is in fact possible to mix OO and FP (ruby is particularly suited for it). You can design systems around classes which are treated as value objects, immutable once initialized, and thus maintain the core tenets of OO and FP in a single program.
I guess the definitions of FP and OO are fuzzy enough that someone will probably come and tell me why this isn't a true Functional architecture, but so far I haven't seen anything close though to a consensus on what the strict requirements of FP really are such that it would preclude any form of binding data and methods. Specific languages are one thing, but when talking about programming paradigms in general I see them as forming a hugely overlapping Venn diagram.
Most modern languages (aka post-C) already have namespaces, why would anyone need to use classes as namespaces? You can just have functions that operate on set of data, keep it tight and clean, no need for classes if you don't need oop.
Imo forcing classes in a non-oop paradigm is just clumsy and, well, unnecessary. When you have a hammer... etc etc
Namespaces in Java, C++, C#, or whatever else is in this family, are simply too dumb. Their sole purpose is to avoid name clashes.
But what if namespaces were values that you could pass around? What if a namespace could be abstract, much like an interface, defining abstract functions and even abstract types? What if you could override the "import" statements a particular module uses?
> no need for classes if you don't need oop
It really depends on what you mean when you say "classes". When I think of classes, I think of subtyping, as in, if type B is a subtype of type A (B <: A), then it means a value of type B can be used in any expression that demands a value of type A (i.e. the substitution rule).
SML has a pretty cool module system that allows for what I say above. But SML modules are special. The cool thing about OOP, when implemented right, is that you don't need special language semantics for special things, as every value can be some sort of object and every type or module can be some sort of class. You know, in a turtles all the way down kind of thing.
You're basically asking what if a namespace were a class.
> Namespaces in Java, C++, C#, or whatever else is in this family, are simply too dumb. Their sole purpose is to avoid name clashes.
That statement is just completely ridiculous. Classes are for inheritance, abstraction and polymorphism. You're saying you think of classes as inheritance and polymorphism without encapsulation, but encapsulation is the key trait for decoupling.
It's been awhile since I worked with SML, but if I recall, the modules you say are cool and special, are cool and special because they give you the three important traits of OOP.
Can you give an example? My thought is you can make a bunch of well abstracted objects, but still have a very tightly coupled system. If one class is free to use the internals of another, it's free to tightly couple itself to another class, regardless of the abstractions involved.
I see where you're coming from though, and I think decoupling is a combination of abstraction and encapsulation, not one or the other exclusively.
If one class is free to use the internals of another, it's free to tightly couple itself to another class, regardless of the abstractions involved.
If one class is free to use the internals of another then it is not a sound abstraction by definition.
I see where you're coming from though, and I think decoupling is a combination of abstraction and encapsulation, not one or the other exclusively.
Your view of abstraction is too limited. Abstraction need not involve classes or objects at all. Abstractions may also be built with modules/namespaces, parametric polymorphism (aka generics) and other forms of ad-hoc polymorphism (such as type classes). All that is required to achieve abstraction is a function or data type which hides its implementation details in a way that allows it to be interchanged freely with other functions or data types that match its interface. Encapsulation is only one way to achieve such hiding.
What three important traits? If you're talking of "inheritance, abstraction, and polymorphism" that you've mentioned, then I'd like to observe that inheritance is orthogonal to OOP, abstraction is essentially named definitions as a language feature (see Abelson, Sussman, Sussman, 1996, Chapter 1), and polymorphism, again, is found in many non-OOP languages, such as Haskell, for example.
Agreed, and in my primary language, C#, these days I would rather just write static classes with static methods and pass in other static methods instead of messing around with interfaces all the time. I write ASP.NET MVC so I don't use state for the most part.
Ok, so I don't do that with controllers for obvious reasons, but my business logic and utilities I try to.
I think of learning OOP much like learning Chess. In Chess it's very easy to learn how the pieces move and start playing, but becoming a master is incredibly difficult, requiring years and years of deliberate practice. When I see people complaining about OOP, it makes me think of two beginners playing Chess, thinking victory is a random outcome.
The dialogue of the article is entirely about coupling, and in OOP figuring out how to make a loosely coupled system is what separates the grand masters from the beginners. Designing a loosely coupled system is incredibly hard and requires years of deliberate practice to master. So when someone who's a beginner or not very good at OO design claims OO isn't a valuable paradigm, I don't listen. Bad OO can be less valuable than no OO, just like anything else, and just like anything else, that doesn't make the whole paradigm bad.
I've explored functional programming in a couple of college courses now, and find it very intriguing. When it's time to go a make a system, however, I find it difficult to boil down functional principles into the system I want. This is a complaint in the same vein as the OO complaints. Functional programming is incredibly impressive when designing a behavior, but what separates the masters from the beginners seems to be when it's time to model an entire system. This is the current separation in my mind: OO is designed and really good for modeling a complex system that involves lots of mutation, and functional programming is really good for modeling and separating behavior within a system.
I don't think we have grounds to say one is better than the other yet. Both OO and Functional have their niches where they're amazingly effective, and both have a giant gray area where it's difficult to mold the pure paradigm onto a solution.
I think the best solution so far is a mixture of the two, such as we see in Scala. Though, a mixture too has its own set of drawbacks. Mainly what seems to be an explosion of language complexity and a giant need for design conventions and limitations when there are so many possible routes to a solution.
The best of the current climate is a mixture of the two, with a framework that clearly defines and separates where and when to use one paradigm over the other, so you aren't bogged down with the additional complexity.
> OO is designed and really good for modeling a complex system that involves lots of mutation
I'd argue that this is where OOP struggles the most. Because mutation in OOP is implicit and unconstrained, it's more difficult to manage than in FP languages, which typically have more sophisticated controls and constraints for handling state.
In chess there are hundreds of annotated databases out there where I can study how the masters play, read analyses of different playing styles, and try to follow along and improve my own intuition.
Is there an equivalent for programming? Github and Stackoverflow are good resources, but it would be interesting to see something more structured. Perhaps a DB of a fixed set of problems such as projecteuler.net with voting and analysis of the submitted solutions.
I've just gotten started, but I'm going to gather solutions to the problems of projecteuler on github. Everyone should feel free to pitch in ones the format to submit on is established. Basically, you will be able to browse the solutions at http://projecteuler.github.io/ by language.
The slide below describing "Type it to the max" seems to describe what you're referring to. It's powerful, but I don't think they are beginner languages.
I Love this comment! The parable to chess is most appropriate. It's easy to learn how each piece works, but to use them properly to create a magnificent system is the work of grand masters.
PS: C makes me feel like this too! I just know how the pieces work.
Not to get into the oo vs functional debate, but generic advice about how big a class should be and how it should relate to other classes is guaranteed to be inappropriate some of the time.
Classes are a tool you can use to design a solution to your particular programming problem. Sometimes the problem with call for a few large classes. Sometimes it will call for an evenly balanced network of small classes. Sometimes there will be a lot of DTOs, and sometime none. Sometimes large classes will cause maintenance problems because there is a lot of complexity confusingly jammed in one place, and other times they will be a boon because you don't have to look in lots of confusingly named files to discover what you are looking for.
Criticizing a design by how well it conforms to abstract rules, rather than criticizing its fit to the actual problem will often lead to confusion.
These rules of thumb are useful, but only as a way to question whether your design is a good fit - I.e. "Would it be better if this class had more behavior or less behavior? Would it be better to split this class into smaller components, or should this group of similar classes be coalesced into a single entity?"
This is a nice narrative. Some people have misconceptions about what OO gives you in comparison to other paradigms. OO provides specific abstractions, which may be useful in many cases, but also may be too much. In some cases, simpler abstractions may be preferable. Less complected (baked together and inseparable) abstractions can often be found in other paradigms. For example, in Java, a person might use a class even though all they need is a namespace. In many functional languages, you can just create a namespace without the other trappings of OO. Sometimes that is just what you want.
I've seen some nice slides from Clojure talks that talk about how many OO abstractions are complected (combined) versions of simpler abstractions. If anyone has seen the slides I'm talking about, please share. For example, a class is combination of a namespace and ___. Possible answers include mutable state, shared data, and so on.
I think one of his most important points is that objects are good for representing, well, actual objects. As in, you have a mouse, or screen or some little robot that you are controlling through code. And that's all they are good for.
Need a way to define a bunch of static methods and avoid name collisions? - you want a namespace.
Need a place to store a bunch of data about something? - you want a associative array.
Need a way to call the same function on different types? - you want an interface.
I'm not sure about this, but I suspect part of the problem stems from the fact that object-oriented languages (usually Java these days) are the first language that students are exposed to. Once you start thinking in OO abstractions, it takes some effort to break out of the mindset.
I've found the most critical difference between OO and FP to be state. Objects are stateful from the moment they are created until they are destroyed. FP is remarkable for its general lack of side-effects - i.e. computations do not normally affect shared state.
I believe this distinction is a critical one because an application with a lot of shared state is more difficult to scale than one without. As another poster pointed out, in a shared addressed space such as a desktop app, this isn't a concern. But if you want to scale an application across multiple address spaces, communicating and synchronizing the state of objects between these spaces tends to be difficult.
So, my tendency is:
Small and shared address space? Tend to use OO patterns and implementations.
Big and distributed address space? Tend to use FP patterns and implementations.
Note, I don't consider OO vs. FP a language choice – I consider it a programming paradigm choice.
The problem is that whether or not we have shared mutable state on a logical level is not for us to choose. It's ultimately a property of the task at hand. Pushing shared state onto the database doesn't make it go away. Workarounds like MapReduce or monads only help us defer the moment of truth. If shared mutable state is what users ultimately expect to see, these approaches are merely simulations of shared mutable state that exploit a time gap in users' perception.
That gap is very brittle. Nightly batch windows become too short. Users expect real time updates leading to major redesigns of entire systems, like Google dropping MapReduce for their search index.
But I think your point about state being the major difference between OO and FP is absolutely true. In OO systems, statefulness is the default everywhere. Nothing is formally known about state in OO system and that is a bad thing. We need state to be explicit and formally specified.
I think OO and FP can coexist peacefully. You just need to pick out their good parts and discard the rest, and use a programming language that allows this.
Do put your object in classes, but don't put behaviors there. Use classes to allow static type checking so that functions taking them can guarantee certain field exist.
Separate behaviors out into its own module. I'd avoid calling them classes here because there will be confusion. Interactions between objects can be implemented in FP style. This is where you get to use map and reduce.
I've been learning about DCI (data context interaction) recently. I can't say I've fully grasped it yet, but it did opened my mind on how to do design differently.
OO itself is just syntactical sugar for standard C structs with functions taking them as args. Substitute in "self" for an additional first argument and you've got FP with types and structs.
Take a look at some OOP C code sometime. Oh, C doesn't have objects? Sorry, I meant a my_struct{} and a bunch of my_struct_op(struct, otherargs) functions.
You seem to be pretty emotionally invested in FP vs any other programming paradigm. I'm more of a "lots of tools in the box" kind of guy.
Bundling a set of methods to a struct in C is not full OO. It lacks inheritance/delegation, encapsulation, polymorphism, and message passing, and I don't know of any definition of OO that doesn't insist on at least one of those.
I just meant that people have very different ideas about what OOP means. You seem to have an unusually broad definition, but others, particularly the guy who coined the phrase in the first place, have more specific definitions. In general, most people agree that OOP involves some form of information hiding, which you seem to disagree on.
I also wouldn't say I'm particularly emotionally invested in FP, just that I'm very skeptical of OOP as a paradigm. OOP languages tend to promote hidden, mutable state, but visible, immutable values are far more useful.
Fair enough.. if you're talking about a giant class hierarchy with lots of mutable state, then I tend to agree that it doesn't usually lead to good software (although things like java's collections library and the outputstream hierarchy are good exceptions).
Do put your object in classes, but don't put behaviors there. Use classes to allow static type checking so that functions taking them can guarantee certain field exist.
Why not just use algebraic data types and modules? Classes are such a verbose, jumbled, complected mess compared to those simpler tools.
> FP is all about passing functions as parameters into other functions.
That's a common misconception. First-class functions and higher-order functions are definitely commonly used in functional programming, but they are not what makes it functional programming. As with any paradigm, there is no universally agreed-upon definition, but most functional programmers will likely agree that lack of mutable state and lack of side effects are much more central to functional programming than first-class and higher-order functions. The latter are really just useful and elegant tools to help accomplish the former.
Not a downvoter, but I believe "Without side effects you couldn't do anything" means that any interesting program ultimately has a side effect. The rocket is launched, the form is printed, pi is displayed on the monitor, etc.
"Function with side effect" is a term of art in functional programming. It's a function which (like all other functions) takes in arguments and puts out arguments (that's the "effect", if you like). But also, with a "side effect" -- the simplest example being, display something on the monitor.
I.e.,
(display "hello world")
returns whatever it returns (we throw it away), but also puts that string on the monitor. The latter action is not just computing a function (in the sense of FP) of the inputs.
Yes true. Running a program with no side effects is a no-op. All side effect free programs are identical, and do nothing. This is one of the most common misconceptions people afraid to learn haskell bring up, because people keep saying "functional programming is having no side effects".
Your claim was "Without side effects you couldn't do anything." I was rebutting that claim. Again, unless you want to claim that libraries "don't do anything."
The more specific claim that you need side effects for a program to do anything is also false. You can prove mathematical theorems with programs that have no side effects. Of course now you'll say that that only works because of the compiler, which does have side effects, but that's really just moving the target so you can be "right" (as you did with this post).
Even if theorem proving isn't good enough, since the entire purpose for having the term "side effects" is to be able to usefully discuss referential transparency, the other poster who noted that IO can be an effect and not necessarily a side effect was correct. If IO is isolated, it is not a side effect, it is just an effect. A good example of this is the execution of the main action in a Haskell program.
> No, of course you can not. How do you see the result? That requires a side effect of some kind.
The result is whether or not the program type-checks.
Your original claim was, again, that "without side effects you can't do anything". So, again, unless you are claiming that functions and libraries "don't do anything", your original claim was incorrect. Your new claim, that programs must have side effects to do anything, is still wrong but more debatable.
You can call me obtuse if you want to, or you can learn how to communicate clearly and say what you actually mean. Doesn't much matter to me either way.
>The result is whether or not the program type-checks.
Ok, you are very confused. Type checking has nothing to do with program execution. Type checking is done at compile time. Any program with no side effects, the compile can safely optimize into nothing, since it does nothing. The compiler does the type checking, not the program.
>Your new claim, that programs must have side effects to do anything, is still wrong but more debatable.
It is the claim I made and you replied to. If you want to be pedantic and obtuse you can't expect people to not call you out on it.
Seriously? I'm directly quoting you in a post that is still visible, and you're denying it? Again, the quote that I originally replied to: "Without side effects you couldn't do anything."
> Ok, you are very confused.
You are both very arrogant and wrong. Not a pretty combination. Thank you oh so much for telling me that type checking is done at compile time, as though there were the slightest possibility I didn't know that given what I've been saying.
If you took some time to learn how to have a conversation on the internet without being an asshole, you might actually learn something. Until then, I'll stop wasting my time trying to have a discussion with you. Cheers.
This is a great piece and probably mirrors the process that a lot of us have gone through over the years. I know it definitely rang true for me.
I would hazard to guess that functional programming or any other solution will go through the same type of evolution and we will be looking back in 10 years at how immaturely we used the technology. Then we will circle back to yet some other technology that was previously used and went out of style, with some new insights added.
It seems to me that we go in a great big circle over and over again, picking up a little bit of new info each time around.
Also applies to the eternal wheel of centralization / decentralization. Or the peculiar 3-phase of interpreted / tokenized / compiled.
On a larger scale paradigms usually pick a spot along the verbosity continuum. Concise and information dense or verbose and information free? This is another eternal cycle. Some modern java looks about the same as 1960s COBOL to me.
A lot of people I know consider object-oriented design orthogonal to the programming style — that the real improvement is moving from a procedural to functional approach. Any system that grows large enough to clearly define an architecture technically is object oriented, so long as it's passing messages among stable interfaces.
You want to have that functional core and imperative shell. Tell above, ask below.
It's just unfortunate that a lot of "object-oriented programming" that you learn in school is really just imperative programming with interfaces ;)
This. I think the debate between "OO vs. FP" is such a fallacy because it's clear you can do both. I attempt to program as functionally as possible within the OO framework that I use, but I also really like extracting functionality into classes and modules when I need to. It just makes sense to do both at once.
Re: "Any system that grows large enough to clearly define an architecture technically is object oriented, so long as it's passing messages among stable interfaces."
@straws: I'm curious, why do you think that the situation you mention above implies that such a system is 'technically' object oriented?
If I'm understanding your claim, I disagree. All you need are mechanisms for:
(1) function-calling or message-passing (I don't think you were trying to emphasize an extremely rigourous definition of 'message passing' were you?)
(2) namespacing
(3) validating a 'stable interface'
There are many definitions of OO, but this is one is good enough for my point: "Objects, which are usually instances of classes, are used to interact with one another to design applications and computer programs" (from http://en.wikipedia.org/wiki/Object-oriented_programming)
You don't need object-based inheritance (e.g. Java) or prototype-based inheritance (e.g. Javascript) to build large systems in this way.
For example, you can build very large systems in Erlang (passing messages too!) without any kind of OO.
There are lots of examples of how functional languages can enforce interfaces without classes, including Haskell.
Erlang processes are basically objects passing messages to each other. They have state and behavior and communicate via message passing, they are objects.
I think we're mostly in agreement, @dj-wonk. Objects are about bundling that state and behavior, nothing more. How they're created or instantiated is secondary.
It's unnecessary to create classes for functionality when it's unnecessary to create objects for functionality. Here's an eloquent article on that subject:
And then you can build a class like LEGO, literally.
Because, it has Class and Roles, a class can extend another class. And a Role can be built with multiple roles. And a class can be built with multiples Roles.
Your OO is outdated if it doesnt implement classes, roles, types and at least method modifiers.
And your language should allow first function class to allow functional programming if you desire.
Perl has all that and comes installed in any unix/linux/mac.. try in your terminal: $ perl -v
Every linux/unix/mac system runs perl. So your company is probably using perl and they dont even know.
I do agree the "noun" vs "verb" analogy when classes are taught are horrible..actually the way classes are taught in general are just confusing.
Ultimately, classes are about code organization and reuse. Done properly, it leads to a well maintained program. Functional programming...it still needs to be organized somehow, whether you put them in classes or namespaces, the last thing you want is spaghetti code. Now depending in the type of programming, you may need to preserve state, which can be quite challenging in a purely functional environment.
I can somewhat relate to the conversations in the article. While I admit that I don't think I used OOP correctly, since I started using more functions instead of classes (in Python, my primary language), I observed that it has been more convenient to reuse and refactor existing code.
Another observation is that it's far easier to read someone else's code if there is no mutation. For eg. I have enrolled for the proglang course[1] on coursera and only yesterday I completed this weeks homework which involves enhancing an already written game of Tetris in Ruby. Major part of the assignment was about reading and understanding the provided code that uses OOP and makes heavy use of mutation. It was quite difficult to understand what one method does without having a picture of the current state of the object, specially with side effecting methods that call other side effecting methods. A few times I had to add print statements here and there to actually understand which one is being when and how many times. While I could score full marks in the end, I am still not confident about completely understanding the code and have a feeling that if I ever need to work with it again, the amount of time it will take to load up everything again into my mind's memory will be equal to what it took for the first time.
Of course, one could only make a true comparison by studying an implementation of Tetris written in FP style. But from my experience with reading and writing code in Erlang (for production) and Racket, Clojure (as hobby) I find it relatively easier to study functional code written by others.
It's not like one paradigm is perfect for everything. But there's one thing that the author wrote that stood out for me: writing a "utils" class for stuff he doesn't "know where it really belongs."
That's one of the benefits of OOP: it forces you to think harder about where things belong, and this really helps as codebases grow. A "utils" class is often a sign that things could be better organized.
I couldn't disagree more that thinking harder about where things belong is a benefit of OOP: it's an entirely unnecessary tax caused by the requirement to wrap every symbol in a class.
Another aspect of this is unnecessary effort expended on deciding if A.operateson(B) or B.operateson(A). Sometimes, you want to Operate(A, B). If your language doesn't allow that, you end up with operator.Operate(A, B).
Classes are not always the best organizational tool.
Will someone give me a few concrete, practical examples of using functional programming in JavaScript?
I'm interested in this concept, but I'm not sure how to go about applying it to my own work as a front-end web developer. Most of the resources online are theoretical.
Everyone using jQuery is benefitting from functional programming. Querying, traversing, and filtering are based on `each` and `map`. It turns what was formerly looping and saving values as temporary/instance variables into declarative, collection-based compositions of functions.
Marijn Haverbeke — a big Lisp hacker and the creator of Codemirror — has a nice chapter in (the free!) Eloquent Javascript as well:
http://eloquentjavascript.net/chapter6.html
You should also check out how Javascript supports functional programming in languages that compile to Javascript such as LiveScript or Elm.
:D. Why is anyone even considering FP vs OO programming a choice? I've used functional patterns within OO before and I'm sure the opposite is possible.
Both have merit depending on the skill-set and staff available to you, scale of project and existing infrastructure.
I was however very disappointed when the author bundled interfaces into the mix as if it was the same as the rest of it. The interface enables you to support many different types of behaviour and enables you to structure your libraries in different ways. It's a different aspect from just classes themselves.
Genuinely I don't think shit like this is healthy. Smacks of religion.
I might be a little late, but does anyone have any examples of simple CRUD web applications written in a functional language? There seems to be a lot of meta discussion about the suitability of functional programming for the web, but not very many concrete examples. Thanks!
'Don’t be silly Jake. Where else are you going to put functions if you don’t have classes?'
The core argument is more of philosophical in nature in terms of paradigms, because from functional programming view a function just acts on a data transforming it to other form. But in pure OO world (like Java) one can not dare to think beyond objects so for them it has to be in a object.
In one of the initial posts of the series, OP mentions that functional programming doesn't have app state. Can anyone point out more details on this?
I don't know if I am understanding it correctly but one of the reasons I absolutely loved Backbone.js was that it kept state of our DOM. Is this relevant to above discussion? because state surely looks pretty beneficial.
After nearly 20 years of Javascript there still is no consensus on how to structure object-oriented systems. This is somewhat reminiscent of lisp's failure to succeed in the marketplace because it's so flexible that no one can agree and get behind enough core ideas to build a large ecosystem. Javascript had the weight of the web behind it to force adoption, but that clearly has been in spite of the OO model not because of it.
I'm not trying to hate on JS either, I think it was a bit of a small miracle what Brendan Eich pulled off under the constraints he had and I'm thankful for JS every day. However I think it's fair to say that something like Ruby's metaclass and mixin system gives you 99% of the modeling power of advanced prototypal modeling with 99% less confusion. Obviously there are ways it could be done better and more explicitly than in Javascript's typeless object world, but even then I don't see where the win is. We need better ways to manage state and mutation in mainstream languages, not more ways to compose objects.
Ever since I started playing around with Java (or even earlier with C++) having data co-mingled with functions has never felt right to me. It just never seemed very extensible to me, especially in a language like Java. Even in C# where you have extension methods, they are second class citizens.
But also the old question of where to put a method seems like unecessary mental overhead for solving a problem. Use modules and just pass things in. You could probably even put some syntactic sugar in the language so that you could use the "object".method notation on the first argument passed in if you really wanted to.
So someone give me clojure with optional typing, a Python-like syntax, and get off my lawn!
IMO, most critics of FP or OOP do so because they lack a full understanding of one or the other. Either paradigm can be applied incorrectly and cause plenty of problems. Both have their merits and strong points. Each fits a different type of project better than the other. Also different people tend to grasp or appreciate one or the other better naturally depending on how their brains are wired. More logical, abstract thinkers tend toward OOP while more right brained people tend toward FP. There is nothing wrong with this. There is also nothing wrong with learning the benefits of the "other side". If fact it is quite beneficial.
some language designers see and fully appreciate the benefits of both. They start putting class synonymous structures in FP languages or FP functionality in OOP languages. This is the future of the mainstream (Ex: EcmaScript 6 has classes/types, C# has Linq, closures, lambdas ETC).
Software design has as a fundamental problem being able to express yourself in a way that is clear and remains clear as things change. Basic principles of good software design, like reducing cohesion, remain good principles no matter what "paradigm" you think you're using. When you change paradigms, be aware that the basic principles remain the same no matter what label you give them. And since design is all about tradeoffs, if you are going to have to violate a principle, you might as well do it in the clearest and most straightforward way possible.
Let me give an example. A singleton is bad for all of the reasons that a global variable is bad. If you need one, I prefer to make it a global variable simply because that is honest about what design principle has been broken. (Unless I want to make it lazy - then it is easier to write with a singleton!)
So learn the principles. Figure out what they look like and are called in your paradigm. Get on with life and solve real problems.
Let me give a concrete example. I learned more about good OO design from the first edition of Code Complete than any other book that I ever read. Even though it was entirely about procedural programming. Because the principles that it discussed, from how to name functions to the value of abstract data types to information hiding, all apply directly to OO. And to functional programming. And to aspect oriented programming. They are truly universal. Learn the principles, they are more important than the labels.