lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Mark Hamburg <mark@grubmah.com> dixit:

> In a sign of how class-based programming contaminated me with respect to prototype-based programming, I've long focused way too heavily on mechanisms like parent* links in Self or whatever the JavaScript equivalent is as a way to do inheritance. Metatables in general in Lua fell into this category of thinking. Some time relatively recently, it finally got through to me that the prototype model is really about duplicating whole objects and that there generally shouldn't be any ongoing relationship between an object and the prototype object from which it was cloned.

If I understand you correctly, then my views are close to yours. I have been contaminated by the prototype virus (mainly from Io). I guess half of the incredible mess and terrible abstraction of typical OO frameworks (meta things, abstract things, virtual things, tons of so-called patterns actually workarounds for language issues) is caused by the root differenciation of classes and regular objects.

> So, what does this mean in general? It means we share metatables because they change rarely and its efficient to do so. We use __index entries either because of a need to implement particular behavior or again because there is a body of essentially unchanging material that we want to make efficient to copy. But thinking in terms of inheritance hierarchies and mechanisms misses the point.

Yo!
The rest of the mess is IMO due to the implementation of inheritance through delegation (forwarding to class, superclass, etc, with all the issues of MRO, and all the complication of creating class trees that are never able to mirror real, non-trivial, problems). Most prototype-based frameworks (including Io's) work exactly like class-based ones on this respect. I think it's an error. I think, actually, that if languages like Io would grow enough to the point of beeing used for loads of heavy projects, then because of that they would meet part of the typical jave/c++ issue and then grow similar warts ;-), under crowd pressure, unless the author blocks every evolution.
Copying methods may seem stupid, but maybe worth it if it allows avoiding all that trouble -- and anyway it's only copying references.

So, I want to experiment with such a scheme:
* An object holds all _specific_ slots. Here, "specific" plainly means what belongs to its "species" (type), including methods indeed. Only generic slots (see below) are not copied at cloning time.
* A new type only differs from a new regular object in that generic stuff (actually references) is also copied. So that there is no real (method and data) lookup: if a slot is not on the object, then it's inside its (direct) type's 'generics' slot. An object's metatable __index field points to it's type's 'generics' slot.

The generics field is here to avoid transmitting to every object all the background "mechanics" stuff that allows objects beeing and behaving as such. Typically, a prototype object system's root Object may hold dozens of such plain "mechanics" methods (the clone method beeing indeed one of them.). We don't want to copy (even references to) them onto every object.
So, the remaining "ongoing relationship between an object and the prototype object from which it was cloned" is only pointing to this generics/mechanics. The lookup stops at the (direct) type, it never goes further.

Also, this generics field makes it easy to alter or specialise the mechanics, in a sense similar to metaclasses -- but infinitely more simply and still on regular objects. Eg implement iteration semantics for all container types, or event fallback mechanism for event-driven things, or graphic operations for visual objects.

I just started some trials, don't know yet if the overall scheme is consistant and handy enough.

> Now, given shared material, what is probably needed is a way to force a deeper clone of the metatable or its __index entry (or other pieces) at clone time. For example:
> 
> 	clone( obj, overrides )
> 
> Where we might write:
> 
> 	clone( obj, {
> 		objfield = "foo",
> 		__metatable = {
> 			__tostring = function( o ) ... end
> 		}
> 	} )
> 
> Mark
> 
> 
Below my current code for cloning, as opposed to subtyping. In addition to what is mentioned above, both (written in code as pseudo-call) allow overriding or creating startup data in the typical Lua way:
   p1 = Point{x=1, y=2}

-- cloning
Thing.generics.clone = function(thing, slots)
	-- new Thing:   th = Thing{...}
	-- * without Thing's generics
	-- * with possible startup slots
	-- * with Thing's metatable
	local newthing = {}

	-- specific slots
	for k,v in pairs(thing) do
		newthing[k] = v
	end
	newthing.generics = nil

	-- startup slots
	if slots then
		for k,v in pairs(slots) do
			newthing[k] = v
		end
	end

	-- metatable
	mt = getmetatable(thing)
	setmetatable(newthing, mt)
	-- type
	newthing.isType = false
	newthing.type = thing.type

	return newthing
end

-- subtyping
Thing.generics.subtype = function(thing, slots)
	-- new type:   th = Thing.subtype{...}
	-- * with Thing's generics
	-- * with possible startup slots
	-- * with Thing's metatable
	local subtype = {}

	-- specific & generic slots
	for k,v in pairs(thing) do
		subtype[k] = v
	end

	-- startup slots
	if slots then
		for k,v in pairs(slots) do
			subtype[k] = v
		end
	end

	-- metatable
	newtype_mt = getmetatable(thing)
	newtype_mt.__index = subtype.generics   -- *** key point ***
	setmetatable(subtype, newtype_mt)

	-- type
	subtype.isType = true
	subtype.type = subtype

	return subtype
end

What is shared, the "body of essentially unchanging material", is not metatables like in your views, but what I call "generics". Concretely, these notions overlap because some of the "mechanincs" indeed maps to metamethods, precisely because metamethods are here for very basic things. But it's not enough: some generic methods may not be metamethods at all; some generic stuff can be data, not behaviour; and some metamethods may not be part of the mechanics, but instead be specific to a type, or even to a single object (even a typed one, not only a singleton).

I'm now considering making types _only_ metatables; as opposed to real working objects. In my present code, Point is a real point like p1, the only difference beeing it holds its own 'generics' and is flagged as isType (and is its own type).

As I just understood thank to Steve Donovan's nice explaination, the alternative makes types lighter (they don't need to work as objects), avoids copying the whole metatable (only __index is needed). But it recreates a deep differenciation between types and regular objects, the consequences of which I'm rather afraid of.
(I don't want the whole mess of java, c++ and recent python & ruby.)

Denis
________________________________

la vita e estrany

http://spir.wikidot.com/