I don’t know if I agree with Zed that indirection and abstraction are constantly mixed up. I think that often indirection creates useful abstractions. For example, in scala, if you want to create an iterable, you only have to implement two things—be able to make an iterator, and say how to make something of the original type you described. After that, you get every item in this list for free, because they rely upon the abstraction of an iterable—as long as we know you can iterate over it, we know that we can do all of these other things, and we don’t really care how else it works.
In that way, I think that an abstract class is supposed to define most of the methods based on a few items which the user is supposed to define, knowing that if the user follows the contract, the rest of the methods will work too. The fundamental way that a class should be able to work has been abstracted (or in zed shaw’s words, simplified) to just a few methods, instead of many, many methods.
I think that Zed Shaw is right that there are many poor interface designers, but I don’t think his reasons for why they aren’t good are right. APIs are very hard to write, and once an API starts to be used, it is very painful to change it. Everyone espouses, measure twice, cut once, but in general, it’s not that big a deal, and you can just change it later if it sucks. People will curse your name if you change your API, so bad APIs, where the author assumed he/she could measure once, cut an arbitrary number of times, ended up very bad.
However, I think that clarifying what indirection and abstraction are, and what they should be used for is good—I just don’t think the vitriol is appropriately placed, and I suspect that better advice for bad API designers would be to think longer before releasing a crappy API.
As Shigeru Miyamoto said, “A delayed game is eventually good; a bad game is bad forever.”
By Zed’s definition, an iterable is not an abstraction, it is an indirection to hook into a rich collection API.
It’s not like Iterable is a bad thing, it is a very powerful construct that can save you a lot of time, so the degree that you end up making your datastructure more complex is more then offset by the benefits.
The rant kind of reminded me of Rich Hickey’s simple made easy talk http://www.infoq.com/presentations/Simple-Made-Easy where he defines the terms simple and complex, then goes to show how many common constructs in programming languages make things easy at the cost of making them more complex.
It seems to me like it simplifies as well as indirects, but maybe I’m conflating easy and simple. The talk looks interesting, but it’s very long, so I probably won’t get to it soon.
I think that often indirection creates useful abstractions.
By Zed’s definiton, that’d be an abstraction, not an indirection. Note that an abstraction says nothing about how it’s implemented.
I’m not certain which part of my comment you’re referring to, so I’ll try to cover all of my bases.
If you’re referring to only the part that you quoted, I don’t think that Zed claims that the two are mutual exclusive, and I think that by “reducing coupling or dependence”, you can often “reduce complexity”. Refactoring code to use dependency injection often does this. Dependency injection lets you just inject the version of something that you’re using, you don’t have to have different code to handle each version of something. If you can indirect a “datastore” away from a “postgres store” and a “mongodb store” away to a storage object with a common interface, you can drastically reduce complexity.
If you’re referring to the part about iterables, it seemed to me like he criticized the idea of abstract classes because in truth they were indirect classes. The specific part of his post that I am referring to is this:
Even more proof comes from the fact that Java uses the keyword “abstract” to create objects which actually support indirection. Think about it, the “abstract” keyword doesn’t reduce, summarize, or generalize a more concrete implementation, but rather creates an indirect path to the real implementation of that function. If this “abstract” class were to follow the previous definitions it would simply reduce the number of functions needed to use the actual concrete implementation. But, when you implement an “abstract” Java class you must implement the same number of functions including the abstract functions just to get it work. Ladies and gentlemen, this is indirection at its finest and abstraction left the building years ago.
My main argument is that it is certainly indirection, but because the two are not mutual exclusive, it can also be abstraction. It in fact does “reduce, summarize, or simplify” a concrete implementation, by making the concrete implementations only have to implement the parts that make them special, and avoid most of the logic. It means that if I want to reason about why my iterable isn’t behaving the way I expect it to, assuming I can trust the library writers, my bug can only be in one of two places—where I say how to build an instance of my class, or where I say how to iterate over it.
Ah. Zed is bad at making this point, and it is a shame because it’s the entire point: abstractions reduce by virtue of composing (indirect) interfaces.
If you can take an implementation and remove all of its dependencies and create the most efficient function you possibly could, then the interface to this function would not be an abstraction. An abstraction does not have any defined performance behavior. It is simply the composition of smaller, well-defined behaviors. You may be able to replace an abstraction with a well-defined interface and implementation that is well-defined to the composed behavior, but the two terms are mutually exclusive: you’d destroy the abstraction when you destroy the composition.
The reason to split interfaces and abstractions into two separate things (and to understand the difference) is that you don’t care about abstraction performance and you refactor interface implementations to gain performance improvements. The abstractions are domain-specific languages that allow you to access the implementations. It’s not indirection because you don’t change the mapping to the implementations of that interface, the interface alone provides the indirection since we can change/improve the implementations. This indirectly benefits code that uses the abstraction, however, which I admit blurs the line, but the indirection occurs after the line drawn at the interface, not at the abstraction, which doesn’t change at all.
I had no idea the terms were so misused! It’s pretty simple, though. You have interfaces… if the implementations change behind them, that doesn’t mean your interface is flexible. It just means you have essentially a commit history that the interface allows you to ignore. Therefore, interfaces are not abstractions. Java misuses the term ‘abstract,’ because their usage only has meaning at the language parse time where it implies the lack of implementation logic. However, at runtime, an abstract class does not provide an abstraction, but an interface. However, we conflate the term with the notion of usage abstraction.
Zed does a terrible job at illustrating a true abstraction. Abstractions take away things. They simplify. They consolidate, perhaps, but they consolidate power through generalization. A programming language is an abstraction of a machine language. A machine language is an interface to the CPU’s control and arithmetic units. A machine language doesn’t abstract because it doesn’t limit.
I’ll add that abstractions are generally not machine-driven. They are people-driven. We abstract because some interfaces are too complicated. We abstract with natural language. We want things to be easier for the sake of programmers. A programming language is an abstraction because ‘var i = j + 1’ is so much better than the machine equivalent, however we sacrifice something to do this. That’s generally a good test for determining which something is. API’s are interfaces (yes, duh), Frameworks/languages are abstractions (read as: should be)
Zed doesn’t really talk about where the discrepancy comes from (because people are dumb isn’t really good enough.) What really gets people is that interfaces usually have implementations which make use of abstractions. So, this is also a question of scoping. An implementation can make use of an abstraction, however, an interface cannot force the usage of an abstraction (since an interface is just a name and a behavior.) A test for that is whether or not the implementation can eschew the abstraction by reinventing the universe to provide equivalent behavior. But looking at the wrong scope when you design can certainly lead to this problem of term discrepancy.
You don’t need to hardcode your implementation though. You can have swappable configurations, so that you just dependency inject the version of an interface that you want at runtime. Both interfaces and abstract classes allow this, so that the way that you think of the implementation should not be as any one individual implementation, because by the time run-time comes around, you don’t have any idea of which implementation is going to be supplied. Someone could even come up with their own implementation of some interface/abstract class, and your code is going to be expected to be interoperable with it. If you aren’t coding against the abstraction, in the human behavior way that you talk about, and you make assumptions about the implementation, you will get screwed.
I would agree that your definition of abstraction is clearer than his, but I claim that indirection can be useful for this too. When I code against an interface, I am buying into the “name and behavior”, that whatever implementation gets supplied to me at runtime, its implementation will make my abstraction correct.
I don’t think that you’re right that APIs are not abstractions. I think that APIs are often abstractions, and that it’s wrong to think of API design as separate from language design. For example, rspec is all just ruby, right? It’s an api for testing. But it is completely an abstraction. Joshua Bloch’s “How to Design a Good API and Why it Matters” talks about API as language design, and is a good watch for anyone who writes libraries that they would like other people to use.
I don’t think that implementations make use of abstractions from Zed’s point of view. I think that to Zed, a good interface is one that makes it easy for you to think of it in terms of a strong (read: simplifying) abstraction. In that sense, implementations use abstractions only to the extent that they must conform to the assumptions the abstraction makes.
I don’t understand your test. It seems like everything trivially passes.
Both interfaces and abstract classes allow this, so that the way that you think of the implementation should not be as any one individual implementation, because by the time run-time comes around, you don’t have any idea of which implementation is going to be supplied.
Java’s abstract classes are not abstractions, they are interfaces and are simply a name, behavior, and are just an agent of indirection. His “abstract interface” and Java’s “abstract class” are two different things.
Interfaces, to Zed and myself, are good when they are complex (have lots of names and behaviors, only minimally are behaviors duplicated within other behaviors), because complexity means that the computation described is flexible. This flexibility is exploited to develop simple languages to compose the smaller behaviors into larger ones: abstractions. That’s the relationship you want when you use interfaces as a means of indirection (communication with the linker), and abstractions (communication with people.)
However, I’ll add that the more interfaces you place between you and a behavior, the more complexity you have, therefore the more necessary becomes an abstraction. Note that you can always reduce a series of interfaces to just one, because more than one indirection can just become one indirection and retain the power of replaceability. So, proper design would focus on a well-defined interface (focused on concise descriptions of behaviors) and a well-defined abstraction separately (focused on how people are going to want to use a composition of behaviors.)
I would agree that your definition of abstraction is clearer than his, but I claim that indirection can be useful for this too.
The reason abstractions are useful over simply interfaces is that indirection cannot simplify. Interfaces are just written to concisely describe computational behavior, but because they are well-defined allow for implementation swapping (indirection.) Some people may be ok with this and use the interface directly. But, they aren’t written for people.
Abstractions benefit from indirection by virtue of the fact that they simplify an interface where such indirection actually takes place.
For example, rspec is all just ruby, right? It’s an api for testing. But it is completely an abstraction.
Alright. To that end, I believe people confuse a dsl (abstraction) with an api (interface). Can they be the same? Or are apis written in an existing dsl? To counter your point, I’d say rspec is a dsl, not an api. I personally have a hard time thinking of rspec as an api. An api is something you build abstractions from, so there must be a line drawn, right? I digress slightly since he’s not talking directly about interfaces vs. abstractions, but rather that apis are agents of indirection where abstractions are composition. But, when you start building for expression instead of concise description of behavior you are concerned about refactoring or swapping out, you are building an abstraction.