Hacker News new | past | comments | ask | show | jobs | submit login
Metaprogramming: Ruby vs. Javascript (fingernailsinoatmeal.com)
38 points by drewolson on Jan 12, 2010 | hide | past | web | favorite | 50 comments

There is no metaprogramming going on in the JavaScript here. Closures are not metaprogramming. It's just programming. I do not want to call names, but the author does not know what he is talking about.

Metaprogramming inside of a running JS system would involve eval() and generating JS code from strings, which you don't want to do. It's prone to exploding in your face and the performance profile is terrible.

Please read the article and think about its quality before voting.

There is no metaprogramming going on in the JavaScript here.

Correct. The author compares Ruby to JavaScript in a situation which requires metaprogramming in Ruby, but not in JavaScript.

Metaprogramming inside of a running JS system would involve eval()

Incorrect. You could implement pre/post conditions in JavaScript by using metaprogramming, and it would not use eval(). The basic idea would be to iterate functions on MyClass.prototype, replacing each one with a new function that calls the original. This would be code (the pre/post implementation) modifying code (the MyClass definition).

Please read the article and think about its quality before voting.

Please calm down, the article is useful to Ruby people who are unfamiliar with JavaScript, and the literal definition of metaprogramming (code that modifies code) is largely misunderstood anyway. The most correctly you could ever use the term would be in reference to runtime or compile time optimization or safety checking, instrumented profiling, or logging, and I rarely ever see it used that way.

I'm the author and I appreciate the insights. I wrote the article about what I consider to be building blocks of metaprogramming. I don't agree at all that eval() is the only piece of JS code that is metaprogramming. I believe that being able to dynamically define methods on objects at runtime is an example of "code that writes code" and hence metaprogrmming. YMMV.

No, that is simply programming with dynamic features. Nearly all modern programming tasks involve this.

If you would like to look at some interesting JavaScript metaprogramming, check out projects like Objective-J/Cappuccino, Clamato, BiwaScheme, HotRuby, and a Python one I'm forgetting the name of (not Pyjamas, that does not run itself in JavaScript, it is compiled by a different language.)

These pre-process some other types of code into native JavaScript structures which execute in the browser or JavaScript interpreter. They are distinct from inserting snippets of live generated code from strings into a system that is already live (as in eval() and friends) to modify it at runtime. That is one of the worst ways to use metaprogramming. What you want is a system that is self-hosting, in order to let you express yourself more deeply in the language. That way, your metaprogramming constructs execute in a controlled environment instead of chaotically determined by various factors at runtime. Lisp is the prime example of this.

In his defense, I'd say that the line between "metaprogramming" and using the features a dynamic language gives you is fuzzy. I'm not sure if I consider changing the semantics of a data type in Lua through its metatable metaprogramming or not, for example.

Generating source code at runtime definitely counts as metaprogramming, but doing that and having to re-parse it is probably the coarsest and most error-prone way. Lisp routes around this by working with ADTs directly, but that forces a lot of other trade-offs in the language design.

What you're describing is a compiler, which is also not metaprogramming.

If a compiler is implemented in the language it compiles, and exposes compilation again to the program it compiles, then the implementation is metaprogrammable.

Your point? You said that someone should look to these systems as cases of "metaprogramming". Now you say that they are "metaprogrammable". You seem to have a very specific notion of metaprogramming, what exactly would you consider to be a prototypical use of it?

Metaprogramming doesn't require eval. Meta programming is being able to ask the program about itself and make changes to it during run time.

No, that is introspection (also known under names such as RTTI, object metadata, and some others). Metaprogramming is the act of programming your programming tools/program itself.

Introspection is a form of meta programming (if you're making choices based on what you learn from introspection).

When we say "meta x" we mean talking about x, e.g. "meta discussions" on HN is discussions about discussions. Meta programming is writing programs about the program, which can also just be doing introspection and making choices based on that.

You are mistaken. If normal programming constructs did not allow the programmer to choose behavior based on input, then every program would have a fixed execution path, would have the same output every time. and metaprogramming itself would not be possible. You are arguing that function arguments are metaprogramming, which is not what the use of the word 'metaprogramming' connotates. Metaprogramming, in the domain of computer programming, specifically carries the intention of programming in such a way that one or more sub-programs are created from the perspective of the one the programmer is working in.

"If normal programming constructs did not allow the programmer to choose behavior based on input, then every program would have a fixed execution path, would have the same output every time. and metaprogramming itself would not be possible."

There is a difference between program control flow based on user input and generating program logic during the course of execution.

I posit that you may have metaprogramming in a progam that has the same output every time.

To me, metaprogramming is writing code which generates or modifies behavior, logic or program flow control. This may happen at compile or run time. Wether generating a closure and setting an attribute on a JS object or using "eval" to define the function, the effect is the same -- the object's implementation was not defined by the original source or environment.

For instance, this is metaprogramming:

def decorate(obj) obj.send :define_method, :yay, (Proc.new{ puts "woohoo!"}) end

a = File.open('tmp.txt','w')

decorated_a = decorate(a) a.yay

Here, there is no eval, but a's implementation changes at runtime. The same is trivially reproduced in JS.

edit: the difference between the original article and this is that the original article makes modifications directly to the original objects, and not to arbitrary objects, which i suppose is why you object to referring to this as meta-programming.

>You are arguing that function arguments are metaprogramming

No, I'm not. We seem to have a terminology mismatch somewhere. Asking a function how many arguments it takes and then taking some action based on that fact would be meta-programming, but taking input isn't.

The number of arguments a function takes is the return value from a function which was passed, as an argument, another function. That is not metaprogramming as it is known by programmers. It is a branch in the program which will be determined at runtime, a feature of computer programming.

>That is not metaprogramming as it is known by programmers.

By what programmers? I have a feeling we are coming from completely different places here.

What do you call what Smalltalk programmers do? In Smalltalk, what we call "meta-programming" happens a great deal but I've never seen eval called in a Smalltalk program ever.

Have you read the book "The art of the meta-object protocol"? Everything in that book is meta-programming yet I don't recall eval being used anywhere, and much of the code was simply to allow programs to discover things about the program itself at runtime.

I have not read that book, but the extensibility features of the CLOS are a world apart from some fixed set of API calls used to make decisions based on object metadata at runtime. CLOS is hugely powerful an entirely extensible down to the core of the Lisp axioms, and nothing at all like some simple set of runtime API calls.

That a kind of metaprogramming is not as powerful as another kind does not prevent it from being metaprogramming.

Some C++ template trickery is clearly metaprogramming - at least, it is to me and people in the C++ community who refer to it as such. It does not give the flexibility that's available in a Lisp, but it's still metaprogramming.

C++ templates are clearly metaprogramming in every way. I cannot think of a case in which they are not metaprogramming. If you brought up C++ metaprogramming with the assumption that I would argue it's not metaprogramming, you were mistaken.

There is no such thing as metaprogramming. To help convince ourselves: http://gilesbowkett.blogspot.com/2009/07/do-you-believe-in-m...

The main problem with the term "metaprogramming" is aptly considered here. "Meta" has been present since programmers started using macroassemblers. We've just added a lot more techniques since then.

Oops, my clumsy fingers put you at 0 points. Could someone correct me?


I've had someone do that to me a time or two. I don't even have the power to down vote them back at this point! :)

PG does some detection of voting rings and nullifies the votes. He does this for upvotes, not sure about downvotes.

I have the feeling that some of mine have been corrected (maybe all where this was happening).

He is adding methods to a class once it already have some instances and those instances get the new functionality. That's 'monkeypatching', which IS a form of metaprogramming.

Your definition of metaprogramming is not the accepted one. http://en.wikipedia.org/wiki/Metaprogramming

I just read it, and it is indeed what I have just said. Unless you would like to quote a specific part of the article, instead of simply pasting a link to a page on Wikipedia?

In my next reply in our conversation I was going to quote exactly this article:

"The language in which the metaprogram is written is called the metalanguage. The language of the programs that are manipulated is called the object language. The ability of a programming language to be its own metalanguage is called reflection or reflexivity."

Reflection is what I was describing. Asking a function its arity is indeed accomplished by passing said function to some other function, but if you want to find the source code for this function look for "reflection" because that's where it should be defined.

"Metaprogramming usually works through one of two ways. The first way is to expose the internals of the run-time engine to the programming code through application programming interfaces (APIs). The second approach is dynamic execution of string expressions that contain programming commands. Thus, 'programs can write programs'. Although both approaches can be used in the same language, most languages tend to lean toward one or the other."

You seem to only accept the second kind as meta-programming. What do you base your description on? Knowing that would make it easier to see where you're coming from and hopefully what the source of the misunderstanding is.

In general, yes, I would consider the second kind as more meta-ish. Even though I think literal strings are a terrible way to accomplish metaprogramming, a set of API calls for introspection is not what I would consider metaprogramming, since they will almost always be accomplished through runtime calls and do not actually generate any other code that can be further manipulated.

Reflection as it is defined in that passage is more of what I am talking about, yes. But I think most languages lack this, including JavaScript. Some good examples are Lisp, Template Haskell, and the macro tools available in OCaml. I have nothing against runtime features, but I do not really consider it metaprogramming if that is the only way to accomplish it, since you are effectively being locked out of a true meta-circular system as a programmer. This style of programming is what defines Lisp and many other languages in the functional realm.

Source introspection, AST manipulation and macro expansion very well may deserve their own term to distinguish them from other forms of dynamic programming. (by that, i don't mean vtables, i mean the other forms of programming your control flow.) However, the term metaprogramming is already defined to mean something more expansive than those two things. I can see how you might want to distinguish the particular forms of manipulation enabled through source modification from the obvious uses of first-class functions, especially if you've gone deep down the rabbit hole, but you're going to have to use a different term.

I just don't think first-class functions are metaprogramming. I have never heard them conflated before, and outside of the author's own writing, have yet to see another example.

Calling the examples just "fist-class functions" is being reductionist. The examples programmatically change the structure of the code. That fits the definition from Wikipedia:

Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data ...

It's not manipulating the program as data. It is simply the program itself. If the program runs with its output being another program which runs, then that is a form of metaprogramming. In this case, the program is simply running. A collection of functions which accept arguments and return values directly is not what most people would consider metaprogramming. It's just a program.

JavaScript is entirely capable of doing metaprogramming, but in the article posted here, it does not.

It's adding structure (methods to classes) at runtime which normally would require writing source code. That is treating the program as data; it manipulates the object system as any other kind of data.

You seem confident that your definition of metaprogramming is the accepted definition, but I don't see evidence of that.

"It's adding structure (methods to classes) at runtime which normally would require writing source code."

-- the linked blog post doesn't do this, though it is possible.

Then what is this?

  sword_symbol = "*********"
  drew.metaclass.send(:define_method, 'swing') do |sound_effect|
    puts "#{name}: #{sword_symbol} #{sound_effect}"
  drew.swing 'slash!!'
I understand that to add the swing method without writing the source code to do it. This use is trivial, but it's easy for me to extrapolate to how one could make the newly created method depend on runtime information.

Ruby, not javascript.

One difference I found is that Javascript has no "method_missing" call. Firefox is ahead of the pack here, as it implements a non-standard __noSuchMethod__ call, but that doesn't exist in the latest chrome or safari.

In addition, there's no way (that I've found) to override the subscript operator [] in javascript.

If you think about it, these two complaints are exactly the same.

I guess in javascript, that's true.

Looks like you'd rather be programming in Lua.

Or Ruby. I've does Lua before, and found it pretty easy, though I hadn't done too much metaprogramming there. But from what I saw, its minimalism is like javascript, and its metatables like ruby's metaclasses and javascript's prototype inheritance.

I think that Lua is what Javascript may have grown into if it had 15 years mature, rather then getting its design prematurely frozen. They seem to have very similar design goals, though where Javascript has the web, Lua has C.

Lua is really powerful, but it's been carefully designed so that a subset of the language can be used for configuration files, data dumps (much like JSON), etc., without having to know anything about programming. The deep elements are there, you just have to look for them. Coroutines, prototypes, metatables, tail-calls, first-class functions, eval, real lambdas, etc. And, did I mention that it mixes well with C?

Yeah, I recently saw that it had coroutines, which was a surprise to me. coroutines can be used to implement fibers and is also related to continuations.

I think it's deceptively simple, so programmers might not think there's much there. In addition, it's pretty minimalistic, you don't get OO out of the box (which isn't necessarily a bad thing, depending on what you're doing), and the number of libraries pales in comparison, all of which deter others for exploring it more fully.

That only means that the field is ripe for some exploration.

It can seem like a weak language since it doesn't have "batteries included", but the whole design revolves around using it as a drop-in library in an existing C / C++ project (which would already have libraries) or extending the language with C. It has a great C API, and it's not hard to write wrappers for C libraries as necessary.

While Lua standalone is a decent language (almost a Python/Scheme hybrid), Lua + C is quite powerful. It's particularly well-suited to the two-level programming style John Ousterhout describes here (http://www.vanderburg.org/OldPages/Tcl/war/0009.html). I haven't used TCL much, but it sounds like the same development style - since you can just use another language when necessary, Lua doesn't have to do everything. It can stay small and simple. While low-level code and hotspots for a project may be written in C, the rest can be written in shell scripts, TCL, Lua, etc. for rapid prototyping. The clear boundary between the layers of development can help keep complexity contained, too.

Wow, who would have thought this article would have started such a contentious thread. An interesting point is that more powerful languages blur the distinction between programming and meta-programming, weaker languages make the distinction more clear.

Perhaps it would be more accurate to say that "metaprogramming" techniques have become more integrated into conventional programming in the language?

This project is pretty awesome: http://jsclass.jcoglan.com/ - Ruby-style JavaScript

I had no idea that you could define a method on a single instance with access to the current scope. I used to always call instance_variable_set before def to get the data I needed in there.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact