For me the most productive programming environments have always exhibited the same pattern. There’s something I think of as kernel space, something else I think of as user space, and most importantly a fluid boundary between them.
For example, my first programming job was writing application software for CD-ROM-based information systems. I wrote that software in a homegrown interpreted language inspired by LISP. The relationship between my software and the engine that powered the interpreter was a two-way street. Sometimes, when I’d find myself repeating a pattern, we’d abstract it and add new primitive constructs to the engine to make the application code cleaner and more efficient. At other times, though, we’d take constructs only available in the engine (kernel space) and export them into the interpreted language (user space). Why? Because user space wasn’t just me acting as a user of the kernel. It was also where the product we were building came into direct contact with its users. We needed to be able to try a lot of different things in user space to find out what would work. Sometimes when we got something working we’d leave it in user space. Other times we’d push it back into the kernel — again, for reasons of clarity and efficiency.
You see the same pattern over and over. In languages like Perl, Python, and Ruby there’s a fluid relationship between the core engines, written in low-level compiled languages, and the libraries written in the dynamic languages supported by the engines.