26
$\begingroup$

A big part of understanding an idea deeply (such as a math theorem, for example) is being able to imagine a thought process which would lead one to invent or discover that idea. Michael Nielsen sometimes calls this "discovery fiction". The goal is not necessarily to understand the true history of how the idea was developed, as the true history might be unnecessarily complicated, and might involve many wrong turns before stumbling onto the right idea. Rather, the goal is to imagine a thought process that would lead someone naturally to the idea, in a way that seems almost obvious.

Question: What thought process would lead to the invention of object-oriented programming?

$\endgroup$
12
  • 2
    $\begingroup$ Alan Kay originally came up with the idea of Smalltalk and what he considers Object Orientated Programming from the study of molecular biology. While it wasn't the start, that was a direct influence on many languages that misunderstood his idea of objects, like, for example - Java. "The Quest to Make Code Work Like Biology" "Oral History of Alan Kay" $\endgroup$
    – Aster
    Commented May 23, 2020 at 0:00
  • 6
    $\begingroup$ It's right there in the name: object-oriented. It is a metaphor. $\endgroup$
    – philipxy
    Commented May 23, 2020 at 9:01
  • 2
    $\begingroup$ I'm guessing OOP was invented by dozens of different people during the 70s. Many people were trying to find ways to streamline the programming process, and using objects, "messages", and related mechanisms were fairly obvious (to some people). It was really a race to the finish line, rather than a single stroke of brilliance. $\endgroup$
    – Hot Licks
    Commented May 24, 2020 at 0:32
  • 1
    $\begingroup$ This question has been answered by Alan Kay himself on Quora: quora.com/… (for reference, Kay invented OOP) $\endgroup$ Commented May 24, 2020 at 3:31
  • 1
    $\begingroup$ @littleO People scrape SO for questions to post on Quora all the time, since you can make money by asking questions. $\endgroup$ Commented May 24, 2020 at 16:36

12 Answers 12

14
$\begingroup$

There are a number of different ideas that are all called OO. I'm going to focus on the Simula-style OO. Here's my fiction which I think isn't too far off from reality:

As computers became more powerful, the amount of entities in an model started to grow. In addition, the different types of entities in these models was also increasing. These models had the concept of the data for these entities which represented their state called 'structures'. They also had the concept of behaviors that could be applied to these entities called 'methods'.

Because these entities were heterogeneous, the data in the structures wasn't always the same. While all the entities might have some data elements such as a position in space, there were other data elements that were specific to some types of entities. This presented a problem. Some methods were common to entities such as movement. But since the data structures had inconsistencies, making sure these common methods worked for all structures was difficult especially when a common method might need to work with properties that didn't exist on all entities. Additionally, there were methods specific to entities and if a method used with an incompatible entity the results were unpredictable and possibly disastrous. While careful programming could overcome these challenges, this was tedious error-prone work. Productivity slowed dramatically as the complexity of the models increased.

What was needed was a way to organize the program in order to eliminate the need to manually keep track of all of these competing concerns. The first idea was to classify these objects into hierarchies. Then the methods wouldn't need to understand every different kind of entity, they would only need to consider the classifications that were relevant to behavior and the structure of the data.

The next idea was to reorganize the methods so that they were aligned to the classes themselves. This removed the need for lots of special cases inside of the methods for different classes. The method was already specific to the class. Within a hierarchy, a class could 'inherit' behavior from a more general class in it's hierarchy or 'override' the behavior to make it more specific to it's needs. Note: inheritance was later determined to create more problems than it solved beyond very limited use.

These approaches resolved most of the problems with the complexity of these models. Now the data structures could be modified for a given entity without having to consider the entire design. New entities i.e. 'classes' could be added with relative ease. OO would continue to be refined but these features established the core of the approach.

$\endgroup$
4
  • 1
    $\begingroup$ I like this explanation a lot! $\endgroup$
    – littleO
    Commented May 22, 2020 at 21:07
  • 2
    $\begingroup$ Be aware, however, that Smalltalk and C appeared the same year (1972). Simula preceded them both. They arose when large and complex computers were programmed in Fortran and Cobol. $\endgroup$
    – Buffy
    Commented May 22, 2020 at 21:55
  • $\begingroup$ Why are answers like this upvoted which lack any credible sources? - There are lots of books written about program language design. For example Bjarne Stroustrup's "the c++ programming language", which goes into detail about the flaws of other language which brought him and others to develop C++. There are probably also for older languages like smalltalk. So we don't have to guess. $\endgroup$
    – paul23
    Commented May 24, 2020 at 9:43
  • 1
    $\begingroup$ @paul23 I think you may have missed the point of the question: "The goal is not necessarily to understand the true history of how the idea was developed" (emphasis mine) $\endgroup$
    – JimmyJames
    Commented May 26, 2020 at 13:45
11
$\begingroup$

Even though you mention fearing the complexity of looking at wrong turns in its development, I think it is still instructive to change the question from "What would lead to the invention of object-oriented programming?" to "What did lead to the invention of object-oriented programming?"

You don't have to give the complete history to pull out the main motivations.

Starting with Simula:

Kristen Nygaard started writing computer simulation programs in 1957. Nygaard saw a need for a better way to describe the heterogeneity and the operation of a system. 1

Then on to the motivation behind Smalltalk's initial development:

Let me [Alan C. Kay] first present the general development of the Smalltalk-72 system up to the transition to Smalltalk-76, and then follow that with the several years of work with children that were the primary motivation for the project. 2

With this interesting tidbit thrown in:

One day, in a typical PARC hallway bullsession, Ted Kaehler, Dan Ingalls, and I were standing around talking about programming languages. The subject of power came up and the two of them wondered how large a language one would have to make to get great power. With as much panache as I could muster, I asserted that you could define the "most powerful language in the world" in "a page of code." They said, "Put up or shut up."

I had originally made the boast because McCarthy's self-describing LISP interpreter was written in itself. It was about "a page", and as far as power goes, LISP was the whole nine-yards for functional languages. I was quite sure I could do the same for object-oriented languages plus be able to do a reasonable syntax for the code a la some of the FLEX machine techniques. 2

Then this realization, also from Alan C. Kay regarding Simula and its influence on Smalltalk:

What I got from Simula was that you could now replace bindings and assignment with goals. The last thing you wanted any programmer to do is mess with internal state even if presented figuratively. Instead, the objects should be presented as sites of higher level behaviors more appropriate for use as dynamic components.

Even the way we taught children (cf. ahead) reflected this way of looking at objects. Not too surprisingly this approach has considerable bearing on the ease of programming, the size of the code needed, the integrity of the design, etc. 2

Now finally, C++

The motivation for creating a new language originated from Stroustrup's experience in programming for his PhD thesis. Stroustrup found that Simula had features that were very helpful for large software development, but the language was too slow for practical use, while BCPL was fast but too low-level to be suitable for large software development. When Stroustrup started working in AT&T Bell Labs, he had the problem of analyzing the UNIX kernel with respect to distributed computing. Remembering his Ph.D. experience, Stroustrup set out to enhance the C language with Simula-like features. 3

So what actually led to the development of OO programming? To summarize:

  • Frustrations with the difficulty of accomplishing a set of tasks with the current tools available.

  • A desire to make building complex, reliable programs easier for others (especially children).

  • A bit of a desire to show off, prove oneself, and overcome seemingly impossible technical hurdles.

I would argue that variations on these three points are what drive nearly every technical innovation in computer science.

$\endgroup$
1
  • 5
    $\begingroup$ Your three bullet points remind me very strongly of the three virtues of a great programmer (the three map quite well to Impatience, Laziness, and Hubris). $\endgroup$
    – KRyan
    Commented May 22, 2020 at 20:29
7
$\begingroup$

At its very core, software development is the skill of taking a complex problem and breaking it down repeatedly until it contains many simple problems.

When explaining the difference between OOP and FP, I often resort to a (massively oversimplified) approach in how both of these approaches break down a complex problem into its constituent parts, but they use a different order of operations.

A customer wants to purchase a product. They contact a vendor, ask for a pricing, buy the product and then the vendor ships the order.

An OOP developer breaks it down like this:

A Customer wants to purchase a Product. They contact a Vendor, ask for a Pricing, buy the Productand then the Vendor ships the Order.

In short, an OOP developer first defines "things that exist" (objects/entities/...), and will at a later stage write the behaviors (interactions between objects).

But an FP developer breaks it down like this:

A customer wants to purchaseProduct. They contactVendor, askForPricing, buyProduct and then the vendor shipsOrder.

In short, a FP developer first defines "things that can be done" (features/functions/actions), and will at a later stage write the necessary data (objects/entities/...) that are needed to chain these actions together.

In both approaches, you will eventually have developed both the actions (purchasing, contacting, ...) and the data (customers, products, ...), but you first defined one before you defined the other. OOP developers start with the data, FP developers start with the actions.


Question: What thought process would lead to the invention of object-oriented programming?

Initially, software development following a scientific context, where it was used mostly to assist scientist in calculating values with better performance and accuracy. At this point, functional programming reigned supreme, as mathematics are inherently defined as a set of functions/operations.
When someone was asked to develop an algorithm, the purpose of the algorithm was explained to them by a physicist/mathematician, and therefore the instructions already following a functional pattern.

However, at some point software started being used in a non-scientific context, for data in/output. In this world, there is a tendency to take a more OOP-like approach by first defining the data records that are expected to contain the data. When someone was asked to develop software for this, they received instruction in an object-like format.

Having to translate the object-like specifications to functional language wasn't easy, but it could be made significantly easier by having the language mirror the specifications (as it reduces the need for complex translations). This is where OOP languages started emerging, which specifically defined the data points before any interactions between them would be discussed.

$\endgroup$
9
  • 3
    $\begingroup$ This is a nice story, but having "been there done that" (and actually using OOP in Fortran IV back in the 1970s, though we didn't know it was called OOP yet) it doesn't match our experience at all. In fact your story is quite unconvincing, because you write the same procedural code either way - you shred it into the same fragments (subroutines, functions, methods, call them what you like) but just package them into different sorts of boxes (modules, classes, etc.) $\endgroup$
    – alephzero
    Commented May 22, 2020 at 19:15
  • 10
    $\begingroup$ … In our case, the starting point was polymorphism - realizing that many different types of entity needed the same functionality, and if the data structures were isolated from the procedural code you only needed one copy of the code to perform the same function on different types of entity. (I deliberately avoided the word "object" there, to avoid mixing up modern ideas with our 50 year old thought processes). $\endgroup$
    – alephzero
    Commented May 22, 2020 at 19:18
  • 8
    $\begingroup$ I think this badly misrepresents the difference between FP and OOP. $\endgroup$
    – Bergi
    Commented May 22, 2020 at 19:51
  • 5
    $\begingroup$ I also wonder if this is the right dichotomy. Shouldn't it be Procedural Programming vs. OOP? Functional Programming is a niche area that most students don't explore until they're fairly advanced. $\endgroup$
    – Barmar
    Commented May 22, 2020 at 22:32
  • 6
    $\begingroup$ With my FP hat on, I'm not happy with this answer at all. Saying that OO developers think about "data first" is arse-backwards -- FP developers think about data in very similar ways, but don't force themselves to think of method dispatch as a consequence of their data structure choice, but rather treat it as a separate concern to be designed on its own merits! $\endgroup$ Commented May 22, 2020 at 22:34
7
$\begingroup$

I did a Computer Science degree before OOP effectively existed, nearly 50 years ago

At that time, the professors felt very constrained by the limited choice of variable types, which was leading to unneeded complexity in programming when trying to carry around complex tightly coupled "objects" inside of code. I quoted "objects", because they weren't using that term at the time.

IMHO The need to create arbitrary "objects" was the driving force behind OOP and the first stage was in languages such as Algol68, which allowed you to create early forms of arbitrary "objects".

Eventually, this led to private and public variables and functions, and then into inheritance and then all the bells and whistles on modern OOP.

$\endgroup$
3
  • $\begingroup$ I still feel constrained by limited types: Where are distance, time, area, speed, ... (statically typed)? $\endgroup$ Commented May 22, 2020 at 23:01
  • $\begingroup$ I still feel constrained by limited OO languages: Where are the pure OO languages, I struggle to think of more than 2 (small-talk and Eiffel), and these are not used much. ( Is Python and JS pure OO? ) $\endgroup$ Commented May 22, 2020 at 23:04
  • $\begingroup$ @ctrl-alt-delor Ruby has no primitives: everything is an object. $\endgroup$ Commented May 23, 2020 at 21:06
6
$\begingroup$

The core notion of the eponymous object emerges from the desire to bundle information which belongs together in structures or records. As COBOL shows, this is a natural projection of tabulated data into databases and computer memory in general. The alternative — keeping each field in a separate variable — becomes a maintenance nightmare which worsens exponentially with the program complexity. Bundling associated data in this way is a paradigm shift towards emphasizing the rows, as opposed to columns, of tabulated data.

Each row can be conceived as a single complex data item, a record, a structure. Once you have structures you write subroutines that take them, or pointers to them, as parameters. That is fundamentally what object orientation is about: Objects consisting of structured data plus operations on them.

The next step is almost minor: You bundle the functions with the data in a supercharged, self-contained structure called a class, so that you can pass the set of allowed functions around together with the data. As a side effect, this obviates the need to pass the specific object as a parameter to the operation because the operation is part of the specific set of data.

Once we realize that the object owns its operations, wouldn't it be nice if we could customize the behavior? Voila, polymorphism.

Later (because this is a painful realization through maintenance cycles) we realize that it is easier to keep behavior compatible than data ("I don't even want to know your true inner reality: I only care about how you present yourself" — almost a Turing test for objects1); interfaces are born.

But it all started with bundling data.


1 And, may I say, such an American attitude. It's Skinner's approach vs. Freud's.

$\endgroup$
4
$\begingroup$

As a former software engineer turned math & CS lecturer, the story that I'm telling all through my CS 1 & 2 courses is one of: Imagine working on a large program with a large team of programmers.

Like: A million-line program with 50 programmers cooperating for 2-3 years, which is approximately the environment I was in with my first engineering job after college. This is a situation which is almost beyond the imagination of my community-college students -- with work and tests done in isolation, and maybe 50-line programs max over the yearlong sequence.

In my perspective, almost all the structures in high-level OOP languages (C++, Java) are in place to support large teams working on large projects. Even simple functions are an overwhelming hurdle for our students about halfway through CS 1; it seems like more typing and more busy-work and more restrictions, for no good reason, in a context when nothing is getting reused or called from multiple locations anyway.

In CS 2 when we hit OOP the data-hiding is likewise completely bafflng when my students first see it. But I try to reiterate the story every day; you've got a million-line program which is far too big (in terms of data, variable identifiers) for any human being to memorize all of it. The data-hiding is done to remove parts of the program and mark them as "you don't need to know or care or memorize this", and remove mental burden from my teammates, and prevent them from accidentally messing up the data in question (enforced by the compiler itself). That's enormous.

I describe inheritance likewise as a way to get new objects written while minimizing the amount of new code that my teammates need to write, and of course polymorphism has a similar story. That said, I've seen multiple thinkers lately assert that those aspects are not super critical (e.g., Go doesn't support inheritance), and it's the data-hiding that was really the shining treasure that makes working cooperatively in large teams possible.

Fred Brooks wrote a follow-up to his essential "No Silver Bullet" essay (included in the revised version of The Mythical Man-Month), which continued the almost total pessimism except with the single bright spot of OOP. Speaking of modularity, encapsulation, inheritance, and strong abstract data-types, he wrote, "The attractiveness of object-oriented approach is that of a multivitamin pill: in one fell swoop (that is, programmer retraining), one gets them all. It is a very promising concept."

$\endgroup$
2
$\begingroup$

While the existing answers are more than enough to answer the question, I'd like to share a very practical example of such a thought process. Consider the good old C type FILE*. This type is an opaque handle to a stream of data, you can read from it, write to it, etc.

But what happens when you try to reuse, for example, fprintf to format a string, except that you want to do it in-memory, rather than writing it to a file descriptor? Eventually the functions fmemopen and open_memstream were standardized in POSIX.1-2008 that allowed you to create a FILE* handle that would write to a supplied in-memory buffer, thus solving the issue.

But there are still so many types of custom streams that we'd like to create. The only way to create a custom FILE* handle is to use an unportable GNU extension fopencookie which allows you to supply pointers to your own custom functions, needed to implement the FILE* behaviour. It has a very convoluted interface, but at least gets the job done.

In the end, we have a rudimentary implementation of polymorphism through several complicated, non-portable functions. Now think of how many other types could benefit from this.

In an OOP language, FILE* would be an interface that anyone could implement. The standard library could provide helpful implementations of File, like FixedBufferFile and DynamicBufferFile, but the users has the power to create their own implementations and pass them to existing functions to achieve code reuse.

$\endgroup$
2
$\begingroup$

The idea of objects in programming predates Alan Kay.

The earliest example that I know of was from Ivan Sutherland's Sketchpad program from 1961.

Sutherland submitted that work for his PhD in 1963. It describes how the system works, and how the system was developed.

In that case, it appears to me that there was a visual concept that turned out to be a useful abstraction in programming.

Sketchpad allowed the user to define a drawn object, and reuse that object in further drawings. You could define an arc, and use it to define an eye. Then you could use two eyes in defining a face.

This is object orientated work. The concept of reusing the drawing elements was then reflected in the organization of the underlying program code. The language itself was not object oriented - Sketchpad was written before there were object oriented languages. I'm not sure what language was used, though it seems there were extensions (macros) written which extended it to make it easier to write Sketchpad.

The preface to that paper (written in 2003 when the PDF was created from the original dissertation) refers to Alan Kay and Simula. It also mentions that Alan Kay's concept of object oriented programming was influenced Simula and Sketchpad - and that Sketchpad and Simula both stem from earlier work by Douglas Ross.

In any case, the concept of objects (though not named that by Sutherland) seems to have gotten into Sketchpad by the process of finding objects in a real situation (drawing) which were then influenced the structure of the code used to represent and manipulate drawings in the computer.

$\endgroup$
2
$\begingroup$

The development of OOPS can be explained with three considerations:

  1. Able to be described so as to be understood
  2. Able to be taught
  3. Able to be enforced

Many people develop great facility with something so as to become experts. For example, I had a great Welding Instructor once. An expert might be able to teach a few people in person, or perhaps not be capable of doing that. If not, everything they learned is eventually lost, and in any case, cannot be generally used.

Fortunately, my Welding Instructor could teach, but also fortunately, we had a textbook, so that he did not have to teach every single thing, and I could refer to the resource later. So, we need a system that can be written about, with standard terms and agreed on conventions.

To build something large, like a bridge or software system, there need to be standards that can be objectively verified and enforced. Everyone needs to be trained. Welders go to Trade School. Programmers learn at a University, on the job, or through courses or self-study. But they ideally all learn the same thing, and apply it uniformly.

So, basically, OOPS came about "because Science". And thus we have its offspring: Bureaucracy. But it is important to realize that, unlike Welding, which has absolute physical constraints (it is based on Engineering), programming is basically arbitrary. The CPU doesn't really care what language you wrote the code in. The language and associated systems have a lot more to do with how human minds work, and most importantly, their limitations. As Edsger Dijkstra said, most of programming is an attempt to compensate for the strictly limited size of our skulls. The people who are best at programming are the people who realize how small their brains are. They are humble.

It takes a humble person to design a large system without building their ego in to it. I am not sure if any of the current paradigms have achieved that.

$\endgroup$
1
  • $\begingroup$ I personally feel that Zaha Hadid designed large things without putting her ego in to them, but perhaps someone can clarify that for me. That's in Architecture. In programming there are probably many good exemplars. $\endgroup$
    – Scott Rowe
    Commented Jul 3, 2022 at 2:30
2
$\begingroup$

As @alephzero said, even though the original Fortran didn't have structs or records, people still created and programmed objects — using a set of arrays, where each array holds one field for all the objects, and thus an object is represented by the same index position in each array.  An object can be passed as a parameter by its index; an object's fields can be accessed using array references.

Object-based programming has been around forever, and OOP represents a long series of little improvements to make object programming easier.

Don't have structs or record (a product type): use arrays.  Don't have tagged unions: use a enum.  Don't have enum's: use an integer.

The thought process is something like: we're doing this programming but it is painful to duplicate this or annoying to write it this way, so why not make linguistic improvement.  Just a long series of removing pain points in the way we program.

For example, in many languages, objects were programmed using structures & unions, and polymorphism was handled using tagged unions (enums) with switch statements (for dispatch).  This works, but has pain points, of having to having to edit numerous switch statements spread throughout the code every time a new enum (class) is added.  Further, back then at least, there is no error give by the compiler if you forget to expand one switch statement.  So, people searched for a way to collect related code together, and reduce these pain points.

Design patterns tend to exist so that we can name and share programming techniques — they are our first formalization.  Parallel arrays are no longer a design pattern because all languages today have some sort of product type.

Today our design patterns include, visitors, double dispatch, containers for dependency injection and inversion of control.  In future we may see languages that either build these concepts in or make them easier to use.

$\endgroup$
3
  • $\begingroup$ This isn't so much an explanation of "objects in FORTRAN" as an explanation of why ad-hoc methods fail and something at a higher level of abstraction (objects or other) became necessary. $\endgroup$
    – Buffy
    Commented May 29, 2020 at 15:25
  • $\begingroup$ No, actually they fail. Not for small programs, but for large programs that must be maintained over time, they just fail. There are too many examples of large failed software projects from, say, the 80s that demonstrate this. Do you remember "throw away programming"? $\endgroup$
    – Buffy
    Commented May 29, 2020 at 15:50
  • $\begingroup$ Yes, new languages address pain points in our programming patterns. $\endgroup$
    – Erik Eidt
    Commented May 29, 2020 at 15:50
1
$\begingroup$

It is like the Unix philosophy: - Write programs that do one thing and do it well. - Write programs to work together. - Write programs to handle text streams, because that is a universal interface (OO languages oppose this rule).

Unix is object oriented, it even has a garbage collector (but written in C, a non OO language).

OO languages make it easier to make and join many small programs (classes).

Classes contain global variables (global to the class, not the program), because globals make it easier, but they don't scale to large programs. Programs change, you may need to improve, or re-use just one part. Therefore create many small programs, and connect them together.

You may do this the Unix way, or via an OO language (depending on which is best for a given job). For example rule 3 of the Unix philosophy stats that components should use text to communicate. This may not always be the best choice. Most Unix components have one input and one output (though this is not fixed), making pipelines easy. But other communication structures are hard. Copying data from one object/process to another, may not be the most efficient solution, OO languages allow just a reference to be passed.

OO also incorporates functional programming, procedural programming, and structured programming (part of procedural, and functional).

I apologise as I have said nothing on the invention of OO, Only on the invention of OO languages. So it would be amiss to not state the obvious. OO languages make it easier to program in OO. But it is also possible (and very common), to use OO languages and not program in an OO way (e.g. use of static in Java).

$\endgroup$
7
  • $\begingroup$ TL;DR Code re-use. As in Hey! I already solved that problem for the last project. How can I use the same code here? $\endgroup$ Commented May 22, 2020 at 23:06
  • $\begingroup$ Unix is the opposite of object-oriented. Except in the trivial sense of every object being a stream. $\endgroup$
    – philipxy
    Commented May 23, 2020 at 8:49
  • 2
    $\begingroup$ @philipxy you should learn about Unix: Gnu/Linux. You will be surprised how OO it is. Some examples of objects, and inheritance hierarchy: processes, files: directories, named pipes, anonymous-pipes, unix-sockets, ip-sockets, devises, symbollic-links; devices: hid, block-storage, memory, camera; block-storage: ide, scsi, network-block-storage; pipes; programs: grep, tail, cut, ps, shell... Then look inside the code, passing a pointer to a structure, a fixed set of functions for the structure. (You can program OO in any language) $\endgroup$ Commented May 23, 2020 at 10:08
  • $\begingroup$ "You can program OO in any language" Exactly--having hierarchiy, state & modules doesn't make a system OO. Those are not "examples of objects, and inheritance hierarchy". And your list of commands demonstrates my point, not yours. $\endgroup$
    – philipxy
    Commented May 23, 2020 at 20:18
  • 1
    $\begingroup$ I fundamentally disagree that Unix is OO. Here's my first hit of my first google, it more or less gives my point of view. Why can't unix be object oriented or at least organized I don't have much more to say about this post. Which I can't make out much of. $\endgroup$
    – philipxy
    Commented May 24, 2020 at 13:42
1
$\begingroup$

I don't know that this is historically accurate but this reminds me of Gestalt psychology, which states that we perceive patterns and "organisations" better than individual components.

Most likely, it must have occurred to multiple programmers of that era that building large programs with multiple distinct entities would be simpler by organizing variables by entity. This approach would still have some difficulties to it if you needed multiple instances of complex entities.

At this point, I think it becomes a natural (but non trivial) thought process evolution to want to create a pattern of methods and members and identify it by a specific name.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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