I understand that abstraction is about taking something more concrete and making it more abstract. That something may be either a data structure or a procedure. For example:
map
is an abstraction of a procedure which performs some set of operations on a list of values to produce an entirely new list of values. It concentrates on the fact that the procedure loops through every item of the list in order to produce a new list and ignores the actual operations performed on every item of the list.So my question is this: how is abstraction any different from generalization? I'm looking for answers primarily related to functional programming. However if there are parallels in object-oriented programming then I would like to learn about those as well.
A very interesting question indeed. I found this article on the topic, which concisely states that:
While abstraction reduces complexity by hiding irrelevant detail, generalization reduces complexity by replacing multiple entities which perform similar functions with a single construct.
Lets take the old example of a system that manages books for a library. A book has tons of properties (number of pages, weight, font size(s), cover,...) but for the purpose of our library we may only need
Book(title, ISBN, borrowed)
We just abstracted from the real books in our library, and only took the properties that interested us in the context of our application.
Generalization on the other hand does not try to remove detail but to make functionality applicable to a wider (more generic) range of items. Generic containers are a very good example for that mindset: You wouldn't want to write an implementation of StringList
, IntList
, and so on, which is why you'd rather write a generic List which applies to all types (like List[T]
in Scala). Note that you haven't abstracted the list, because you didn't remove any details or operations, you just made them generically applicable to all your types.
@dtldarek's answer is really a very good illustration! Based on it, here's some code that might provide further clarification.
Remeber the Book
I mentioned? Of course there are other things in a library that one can borrow (I'll call the set of all those objects Borrowable
even though that probably isn't even a word :D):
All of these items will have an abstract representation in our database and business logic, probably similar to that of our Book
. Additionally, we might define a trait that is common to all Borrowable
s:
trait Borrowable {
def itemId:Long
}
We could then write generalized logic that applies to all Borrowable
s (at that point we don't care if its a book or a magazine):
object Library {
def lend(b:Borrowable, c:Customer):Receipt = ...
[...]
}
To summarize: We stored an abstract representation of all the books, magazines and DVDs in our database, because an exact representation is neither feasible nor necessary. We then went ahead and said
It doesn't matter whether a book, a magazine or a DVD is borrowed by a customer. It's always the same process.
Thus we generalized the operation of borrowing an item, by defining all things that one can borrow as Borrowable
s.