Metaprogramming is absolutely necessary for a good (general purpose) programming language, CMV

It doesn't have to be full-blown macros, but some kind of metaprogramming, such a closures, is necessary to make the language sufficiently extensible.

Edit: well, one thing I learned is that people don't consider Higher Order Functions metaprogramming, which, to me, is weird, but I guess that's a thing.

Edit2: In fact, people really don't want to call HOFs metaprogramming.

Posted May 17, 2013, 10:18 AM UTC · edited

938
anvsdt•

Sidenote: There's absolutely nothing meta about closures/higher order functions so I won't argue against them, metaprogramming should strictly mean staged code generation, even if the staging isn't as refined/powerful as MetaML/Racket/Scheme or even CL. I feel like I should do a CMV about this.

While metaprogramming is a really convenient, you can get by a lot with the right combination of higher order functions, purity, totality, (dependent) types and laziness.
Why HOFs are useful here, hopefully, should be preaching the choir. Constructs that required macros without them (while, for, ...) can be rewritten easily as a HOF.
Laziness helps for "short-circuiting" constructs such as if, and, or, ..., and is quite well known as well.
Purity and types let you encode many meta-ish constructs in the language itself. In Haskell you see often people talking about "base functors" and "Fix", which let you represent datatypes and write generic programs on those datatypes (like generalized fmap, fold, ...). With dependent types, you can encode a micro type system in the type system (called a universe), write generic programs on the representation of types, and bring them back into the "normal" types. Totality just helps with dependent types.

As a point in my favor, Haskell's doing pretty well without metaprogramming, the most common use of Template Haskell is in the lens library, but even there it's just a convenience macro and not necessary.

4
tailcalledOP•

I agree with most of that, except that I would claim closures are a kind of metaprogramming. First, there's the fact that they give many of the practical advantages that macros do. Secondly, on a more theoretical note, closures are the internal Hom-sets of a category, what could be more meta than that?

1
anvsdt•

First, there's the fact that they give many of the practical advantages that macros do.

Well, that's cheating. Laziness also gives many of the same advantages, or even anonymous classes in Java, but I wouldn't call them meta.

what could be more meta than that?

Something that, taken as a logical construct, lets you encode the ω-rule, a metalanguage which can manipolate the syntax of the language in the language, a metatheory in the theory.

2
tailcalledOP•

Well, that's cheating. Laziness also gives many of the same advantages, or even anonymous classes in Java, but I wouldn't call them meta.

Laziness is, for the purposes we are talking about, slightly more convenient lambdas (for certain things). Anonymous classes in Java are, for the purposes we are talking about, much more inconvenient lambdas.

Something that, taken as a logical construct, lets you encode the ω-rule, a metalanguage which can manipolate the syntax of the language in the language, a metatheory in the theory.

Depending on how you Curry-Howard the omega-rule, that can mean multiple things. Can I see an implementation using macros?

-1
anvsdt•

Laziness is, for the purposes we are talking about, slightly more convenient lambdas (for certain things). Anonymous classes in Java are, for the purposes we are talking about, much more inconvenient lambdas.

That's like saying that strings are really inconvenient anonymous functions because you can write an eval for strings that represent lambda terms. How much is too much?

Depending on how you Curry-Howard the omega-rule, that can mean multiple things. Can I see an implementation using macros?

In MetaML/Agda-ish syntax, it would (probably, I'm not an expert) have one of these two types:

-- for any predicate P, if you can construct a term of type P n for all n, you can construct a term (n : Nat) -> P n
ω-rule1 : (P : Nat -> Type) -> ((n : Nat) -> Code (P n)) -> Code ((n : Nat) -> P n)
ω-rule1 P f = <λn -> ??>
-- for any predicate P, if you can construct a term of type P n for all n, then P n for all n.
ω-rule2 : (P : Nat -> Type) -> ((n : Nat) -> Code (P n)) -> (n : Nat) -> P n
ω-rule2 P f n = run (f n)
2
julesjacobs•

There are two kinds of metaprogramming here. One is code generation, like you can do in MetaML, Lisp, etc. The other is introducing syntactic sugar, like C macros, Lisp macros, camlp4, and I suppose closures and laziness can in some cases be used to similar effect. From a language design perspective, it's a good idea to separate the two. Code generation is purely about performance. Syntactic sugar is purely about convenience in expression.

Now it is the case that in some languages, you can express things with code generation that you cannot express without it. For example in template haskell you can generate new data types, and in that way you can express things that you couldn't otherwise. IMO that is a bad conflation of concerns. You should be able to remove the staging and end up with working code (except possibly slower). For example in dependently typed languages you can; since types are just values you can generate new types with ordinary expressions like you can with quotation expressions in template haskell.

1
rpglover64•

[Devil's advocate]

Java is a good programming language despite lacking any metaprogramming facilities (it got closures recently, but I agree with u/anvsdt that they're not metaprogramming). It has been around for about 20 years and sees wide use, is the second most popular langage (according to tiobe), behind only C, and is the go-to language in industry for writing large long-term projects.

If one absolutely needs metaprogramming (e.g. the amount of boilerplate becomes unreasonable otherwise), one can just generate the java code (as a co-worker of mind once did).

1
tailcalledOP•

Java is a good programming language

I disagree. Popularity does not mean something is good.

2
rpglover64•

Then please clarify what "good" means. By a certain metric (i.e. that of widespread use), Java is one of the best programming languages. You reject that metric, so I challenge you to find another, more principled one.

1
tailcalledOP•

Productivity? That is, how fast a program converges to what the users need.

1
rpglover64•

Productivity for whom? Productivity for novices, for experts in the language but not the domain, for experts in the domain but not the language, and for devlopers working in domains where there are insufficient libraries all differ from productivity of domain experts with java experience solving problems where there exist good libraries.

Even glossing over that, as you go from developing "scripts" to developing "programs" to developing "applications" to developing "enterprise applications" (i.e. as overall complexity and code size increases), it becomes more controvertial that Java is less productive than other languages.

1
bheklilr•

You say "a good (general purpose) programming language"

Java may not be what you consider a good language, but many people do, and its continued popularity speaks to its ability to being general purpose. I personally wouldn't consider C to be a well designed language, along with Java, and wouldn't call it "good", but I would call it necessary, powerful, useful, and general purpose. I see the use of the language, but it doesn't mean I like programming with it.

1
tailcalledOP•

Well, my title did not say "metaprogramming is absolutely necessary for a necessary, powerful, useful general purpose programming language".

2
bheklilr•

My point is that what you consider "good" is subjective. A language can still be used to build incredibly complex systems whether or not you think it's a good language. If you can provide a definition and rationalization of what constitutes a "good" programming language, then I'm all ears. But if you can't come up with a definition and a defense for it, I would suggest broadening your definition until it is defensible.

1
tailcalledOP•

Also, note that Java does in fact have metaprogramming. Both reflection, proxy-reflection, anonymous classes, iterators (believe me, they can be used for a bit of what one can call metaprogramming) and now closures. See my arguments above for why closures/anonymous classes are metaprogramming.

1
rpglover64•

I believe your (nonstandard) definition of metaprogramming is too permissive (although I grant that reflection is possibly a form of metaprogramming). Can you provide an example of a bad general purpose programming language without metaprogramming in your sense?

4
tailcalledOP•

Well, I believe every general-purpose language we have today is bad (you do not want design errors in your foundations!). I can strengthen my claim to say that more metaprogramming -> more productivity, up to a certain limit (not sure if that limit is above or below Lisp-style macros, but it is below Z-style macros).

1
rpglover64•

Can you give an example of a modern language that does not have metaprogramming facilities?

2
fread2281•

Hey, don't insult python's design!

1
BonSequitur•

There's really no good definition of what metaprogramming is. Some people would call all code generation metaprogramming, to the point that systems like lexer generators and EDSL are metaprogramming. Is Moose (The object-orientation system popular in Perl) metaprogramming? Most of its functions are code generators (E.g., it supplies a has function that generates accessors for object attributes). But, is Ruby's attr statement metaprogramming, then? Or is it just syntactic sugar?

I don't think closures are metaprogramming (Though no language without closures is really worth writing code in, but that's another story). Whether or not HoFs count depends on who you ask, but if they count, then you're right - a good general-purpose language should have them.

I'm going to define metaprogramming as the ability to define new semantic language constructs. And I think that yes, some level of that is absolutely necessary, but it's an open argument whether you need only C-style preprocessor macros, or full-blown Lisp-style macro systems.

On some level, all programming is 'metaprogramming,' of course. Compilers are programs to write machine code for us, and all the way up the chain to interpreters, virtual machines, byte codes and so on. Code generation is probably the only way to manage complexity; every sufficiently complex system ends up being implemented in a DSL built for the purpose, hopefully a good one.

So, your question is largely a matter of how metaprogramming is to be defined. If you define it broadly enough, then it's obviously true. If you define it narrowly enough, then it's a position only really cranky Lisp partisans would take.

Edit: Also, whether or not the language you're writing in admits it, code is data and that's always true, so at least on an abstract, theoretical level, you can do metaprogramming in any language because you can always write a code generator for it. That counts, right? Even if you can't get reflection/self-modifying code out of it. Assuming generative programming counts, then your question is meaningless - any language capable of text I/O can do metaprogramming, up to and including shell scripts and Brainfuck.

1