Functional Thought Experiment Pervasive metaprogramming as a metaphor for pervasive mutable state
Imagine, if you will, that you are interviewing a young programmer for a software engineering job. She’s bright, she’s friendly, and she can solve all your interview whiteboard questions easily.
But she solves every problem with reflection, even in cases where reflection is completely unsuitable and adds unneeded complexity, and probably bugs to the program. (Assume it’s a Java shop as well.)
It doesn’t matter what problem you give her.
Reverse a string. Implement a linked list. Sort an array in better than 0(n^2) time. Implement Hello World. For every problem, no matter how hard or how trivial, it’s written as a dynamic run time compiled metaprogram. You ask her not to use reflection, and she just blinks at you, thinks really hard for a moment, and confesses that she has no idea what you are talking about, then cheerfully offers to implement the programs entirely in LISP macros.
You probably don’t hire her. I mean, yeah it’s cool and impressive that she can metaprogram her way through anything. But if the business requirement is to sum a list, we really need her to just sum the list, you know with a sum function or a for loop or really anything other than a runtime compiled metaprogram. We’d like her to write a program to sum the list, not write a program to write a program to sum the list.
Okay, now imagine that due to a confluence of factors, everyone is that bright young programmer, and have been for forty years. Everything everywhere is implemented in Java’s reflection or LISP macros or whatever dynamic runtime metaprogramming model you choose: people use it for everything. The first tutorial in every programming language is how to write a program that writes a program that prints “Hello World.” Socially awkward twelve-year-old boys around the world take the next step themselves and write metaprograms that write metaprograms that print “Hello Farthead.”
Of course, bugs are all over the place. Metaprogramming is harder than programming and forty years of tooling still haven’t changed that. Young programmers start out feeling like super L33t hackers, and after years of dealing with multi-million line codebases, all being metaprogrammed on by other caffeine-infused super L33ts, they all come out the other end of their careers with small farms in the country and hopes that it doesn’t all come crashing down before they can get at their 401ks.
Of course, there is one language that doesn’t start with metaprogramming. That language says “Metaprogramming is cool, and our language supports it, but we default to static compiled unchanging code. If you need metaprogramming you will need to use our metaprogramming sublanguage.”
No one uses that language though. They say things like, “Sure, I can sum my list in static undynamic code, but your metaprogramming sublanguage is a pain. Sometimes, I just need to dynamically generate a new program halfway through my sum function.” And both sides roll their eyes at the obvious density of the other.
This is how things look now from the perspective of the Haskell language, and functional programming in general. Stateful programming is hard. It is harder in a multithreaded environment, and even harder on a multiprocessor environment, and even harder in a multithreaded, multiprocessor, multi-server environment with the code being pounded on day-in and day-out by dozens of caffeine-infused super L33t kids, and forty years of tooling haven’t helped much. Much of OO developed as a way to escape the mess of globally mutable state in preference for small chunks of locally mutable state, with the mutating code residing conceptually nearby (i.e., in the same small file or in a clearly defined inheritance hierarchy).
Practically alone, Haskell says, “We realize that many problems require a mutating state, indeed that you can view the entire universe outside the application as an astronomically complex finite state machine. But most problems do not require mutating state, or require only extremely small and highly-localized bits of it. So, where you need it, you have it. But most of the time, you don’t, so we will default to non-stateful, pure, 100% deterministic code.”
Sure enough, if you are summing a list, you can make the entire universe read-only except for one accumulator. And in fact much of what programmers do consists of reading some sort of broadly defined “list” (even if it’s a list of input events) from the universe, accumulating it in some way, a returning the result to the universe. You do not need global read access to the universe at every moment and at every line of code for this. And you sure as hell don’t need global write-access.
But, then you meet the guys that say “Yeah, but I really want to stop in the middle of summing this list and ask the user if he would like to play a game of solitaire while he waits, and if he says yes, start up the solitaire subroutine. You know, while I still sum my list, and I really want them to be in the same function call, just because it’s easier that way.” And everyone rolls their eyes at everyone else’s density.
blog comments powered by Disqus