By far the best explanations of programming paradigms are found in Peter van Roy's works. Especially in the book Concepts, Techniques, and Models of Computer Programming by Peter Van Roy and Seif Haridi. (Here's the companion wiki.) CTM uses the multi-paradigm Distributed Oz programming language to introduce all the major programming paradigms.
Peter van Roy also made this amazing poster that shows the 34 major paradigms and their relations and positions on various axis. The poster is basically an incredibly compressed version of CTM. A more thorough explanation of that poster is contained in the article Programming Paradigms for Dummies: What Every Programmer Should Know which appeared as a chapter in the book New Computational Paradigms for Computer Music, edited by G. Assayag and A. Gerzso.
Another great book that demonstrates several major programming paradigms is Structure and Interpretation of Computer Programs by Harold Abelson and Gerald Jay Sussman. This book was the basis of MIT's CS101 for several decades. A course taught by Abelson and Sussman themselves was recorded at a corporate training for Hewlett-Packard in 1986.
The main difference between CTM and SICP is that CTM demonstrates most major paradigms using a language that supports them (mostly Distributed Oz, but also some others). SICP OTOH demonstrates them by implementing them in a language that does not support them natively (a subset of Scheme). Seeing Object-Orientation implemented in a dozen or so lines of code is friggin' awesome.
You can find video recordings and course materials from the Spring 2005 course on MIT's OpenCourseWare website. Another recording of the course from MIT's short-lived ArsDigita University project. SICP has also been taught at other universities, in fact it is being taught at Berkley right now.
On a personal note, my own experience has been that really understanding a programming paradigm is only possible
- one paradigm at a time and
- in languages which force you into the paradigm
Ideally, you would use a language which takes the paradigm to the extreme. In multi-paradigm languages, it is much too easy to "cheat" and fall back on a paradigm that you are more comfortable with. And using a paradigm as a library is only really possible in languages like Scheme which are specifically designed for this kind of programming. Learning lazy functional programming in Java, for example, is not a good idea, although there are libraries for that.
Here's some of my favorites:
- object-orientation in general: Self
- prototype-based object-orientation: Self
- class-based object-orientation: Newspeak
- static class-based object-orientation: Eiffel
- multiple dispatch based OO: Dylan
- functional + object-orientation: Scala
- functional programming: Haskell
- pure functional programming: Haskell
- lazy pure functional programming: Haskell
- static functional programming: Haskell
- dynamic functional programming: Clojure
- imperative programming: Lua
- concurrent programming: Clojure
- message-passing concurrent programming: Erlang
- metaprogramming: Scheme
- language-oriented programming: Intentional Domain Workbench