Structure and Interpretation of Computer Programs is probably the best book to read for somebody who already knows a decent amount of programming on a practical level, but would like to grok the idea and theory of programming on a deeper level. That deeper understanding if left to stew will turn into a more subtle and efficient approach to program design and problem solving.
The first section starts out with familair contstructs like simple mathmatical formulas and expressions that are in a way simple programs on their own. It guides the reader through the theory behind evaluating expressions. From there the book takes the reader through more complex constructs, including the ideas of abstraction and recursion. As the book progresses, the reader is coached through the design and implementation of a simple interpreter to evaluate expressions, building up to the idea of creating your own programming language.
The middle portion of the book tackles some problems that illustrate how much flexibility and power can be harnessed by carefully identifying the problem and creating a language that expresses both the problem and the solution. You can see everyday examples of very powerful software created in such a manner if you look at things like Alias Wavefront (a good portion of that is written in Scheme and the user can customize it to no end), emacs (which also uses a variant of Lisp), and sendmail which uses it's own special configuration language to provide the robust and flexible email infrastructure we rely on daily.
The final portion of the book takes all the things we've learned so far and puts them together and in so doing prepares us to go full circle and end up back and the very beginning of computing itself, but this time with a much deeper understanding. In this section the book takes us through the design of a simple logic simulator that contains the building blocks of a modern register machine (a CPU). The simulator is programmed through an input file that contains a list of functional blocks and their interconnections. The book shows us that a complex enough input file for that simulator will generate a simple processor. Once we have our CPU, we next write an assembler to create programs for the simulated proccessor.
The final task is to write a compiler for the virtual machine that we have just constructed, which will take a high level language and turn it into our assembly language, which once assembled can be run using our simulated processor.
The beauty of this approach to teaching the basics of programming is that it is very well rounded. The neat thing about this book is that it starts out with concrete stuff that pretty much anybody can grasp, and works its way towards the theoretical problems while still providing concrete examples. I would say that any programmer with the time should give this book a read (or several) because it will save you the time it took to read many times over in the course of your work, and that aside, it's fun and well written.
As sketerpot points out, the entire book is online for public use at the following URL: