Unrefactorabling
Jul. 2nd, 2018 05:36 pmMy dislike for code generation (macros, templates, preprocessors, etc.) was always more on the abstract side. My main argument was that while functions are defined in terms of what they do when they are executed, macros (or other similar things) are defined in terns of what code they generate. And for me the code is a tool, not an end goal. I don't want to think about code when I'm writing code. I want to think of what should be done.
Recently, however, I realized there might be a good argument on the practical side. And, what's funny, it's pretty much the same argument as the one against dynamic typing. So, here it is.
Let's say, I have a certain structure. I mean, structure like in C: a collection of seemingly unrelated data of different types. For (a textbook) example: structure describing a student in the college database: it contains the student's ID number (integer), his name (string), his age (integer), the courses he's taking (array of course IDs) etc. At some point, while developing/maintaining the system, I decide I no longer need one of those parameters — say, age.
Now, in a statically-typed language I would normally remove this field from the structure. Then I would compile the program, and, if it compiles, I just know that this field was not used anywhere. If it was used, the compiler would show me an error message, from which I (assuming I have some experience) would be able to deduce where exactly this field is used. Then I would either remove this part (it could be insignificant, or circular: for example, when a new record is created, copying all the data from the old one, including age — sure, it uses this field, but it doesn't have to), or conclude that I was wrong and some more thinking is required.
In a dynamically-typed language it's very different. True story: I've asked our front-end devs once to switch IDs used in the system from integers to strings (because sometimes they don't fit into Javascript' numeric type). They agreed that it should be done. It was half a year ago. It's not done yet. In fact, it's such a daunting task, that they can't allocate enough resources to do it without bringing everything else to a halt.
And then we have our code generation stuff. And suddenly the problem is here again. For example, if there is something like
Now, this does not apply to macros only. In fact, there are lots of other things that make code unrefactorable. Implicits in Scala, class instances and derivations in Haskell. The latter is partially mitigated by several factors:
1) Standard
2)
3)
4) Not exporting constructors is usually advised, so at least automatic derivation based on the structure's internals is limited to a single file.
Still, it's a problem. It's not why I dislike code generation — at least not the main reason — but it might be why you do.
Recently, however, I realized there might be a good argument on the practical side. And, what's funny, it's pretty much the same argument as the one against dynamic typing. So, here it is.
Let's say, I have a certain structure. I mean, structure like in C: a collection of seemingly unrelated data of different types. For (a textbook) example: structure describing a student in the college database: it contains the student's ID number (integer), his name (string), his age (integer), the courses he's taking (array of course IDs) etc. At some point, while developing/maintaining the system, I decide I no longer need one of those parameters — say, age.
Now, in a statically-typed language I would normally remove this field from the structure. Then I would compile the program, and, if it compiles, I just know that this field was not used anywhere. If it was used, the compiler would show me an error message, from which I (assuming I have some experience) would be able to deduce where exactly this field is used. Then I would either remove this part (it could be insignificant, or circular: for example, when a new record is created, copying all the data from the old one, including age — sure, it uses this field, but it doesn't have to), or conclude that I was wrong and some more thinking is required.
In a dynamically-typed language it's very different. True story: I've asked our front-end devs once to switch IDs used in the system from integers to strings (because sometimes they don't fit into Javascript' numeric type). They agreed that it should be done. It was half a year ago. It's not done yet. In fact, it's such a daunting task, that they can't allocate enough resources to do it without bringing everything else to a halt.
And then we have our code generation stuff. And suddenly the problem is here again. For example, if there is something like
val MyStructureFormat = Json.format[MyStructure], then, after I remove some field from MyStructure, it would just regenerate the JSON reader and writer. Without telling me. And now I've lost control over it. My program compiles — but it's output have been altered in such a way that some other program might have troubles working with it. And I have no clue it could happen at all.Now, this does not apply to macros only. In fact, there are lots of other things that make code unrefactorable. Implicits in Scala, class instances and derivations in Haskell. The latter is partially mitigated by several factors:
1) Standard
Show and Read classes are strongly recommended to be used for debugging only.2)
Functor class (which is derivable with a certain Haskell extension) can have only ONE instance, at least if we want it to conform to the laws; so, at least automatically generated `map` function is probably the same we would write by hand.3)
Eq has more than one implementation, but usually only a single sensible one.4) Not exporting constructors is usually advised, so at least automatic derivation based on the structure's internals is limited to a single file.
Still, it's a problem. It's not why I dislike code generation — at least not the main reason — but it might be why you do.