-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Class inheritance syntax doesn't work as expected #347
Comments
Hi,
There are two problems I see here. First off, the docs seem vague to me around what "c" means. I don't feel certain I know what "optional table to be used as class" means. Second off, the doc defines Reading the source, I find that if a plain table with no metatable is passed as the first argument to class(), then rather than If I rewrite the code to not use the
I get:
Which is exactly your expected behavior. So, speaking as one user of the library to another, my advice is: specify the class members using the normal syntax and it should work, and I would avoid the … This is probably not very satisfying to you since you are doing fiddling-inside-the-abstraction stuff like calling tablex.update (which I personally would probably not do, because I could not be sure whatever it is you just did would work properly in future versions of Penlight). So, let's take one more look inside If we look in the implementation for
So basically, So if we think about these steps for a moment, it's immediately obvious why your original sample code doesn't do what you expect it to. When you construct
I don't know what My personal opinion, I think that what Penlight is doing is basically reasonable, that your use of "c" is not appropriate for what I understand "c" to be for (that is: allowing you to set metatable keys for your newly-constructed object) and I think if there is a problem at Penlight's end it is that it needs to document |
Follow-up— Documenting
|
Thank you very much for the analysis @mcclure, that's helped me make sense of a few bits I was flat out reading wrong in the code. However as of right now I'm even less convinced than before that the current inheritance behavior is correct.
Yes that's what's happening, but doesn't that seem backwards to you? I cannot imagine a situation where I want to create a new class Bar than inherits from class Foo but the things I explicitly define in Bar get overwritten with things from Foo. The whole point of inheritance should go the other way, no? I do agree that the nature of Here's an interesting one. There is one line of code in What's weird about this to me is I don't understand why the nature of my base table as a Penlight class or a plain table would ever reverse the direction that I wanted class inheritance to happen! In my initial examples the 1st (foo) and 5th (zar) classes are being detected as plain, the others are not. In the 1st case of foo, there is nothing to inherit from so nothing goes wrong. In the 5th case zar the base class is being detected as plain (true enough) but that's causing a reversal in the inheritance direction. What would ever be the use case for that? If I apply this patch, everything works as expected for all of my examples: diff --git a/lua/pl/class.lua b/lua/pl/class.lua
index 49246ee..e402b67 100644
--- a/lua/pl/class.lua
+++ b/lua/pl/class.lua
@@ -141,7 +141,7 @@ local function _class(base,c_arg,c)
if type(base) == 'table' then
-- our new class is a shallow copy of the base class!
-- but be careful not to wipe out any methods we have been given at this point!
- tupdate(c,base,plain)
+ tupdate(c,base,true)
c._base = base
-- inherit the 'not found' handler, if present
if rawget(c,'_handler') then mt.__index = c._handler end Now I get:
In the final example 5 (zar) the Of course to actually fix this I would dig in and clean up a little deeper, but first help me think about why the inheritance would ever go backwards and the base class would override something set in the class being defined. After we agree on the direction I'll review the conflation between regular and meta tables, it seems to me the docs are calling something a metatable that isn't at all. |
"but first help me think about why the inheritance would ever go backwards" It depends on what the "c" argument was originally meant to do. If "c" is meant to be "here are the fields of the new class/metatable", then no, it doesn't really make any sense. One possible usage of "c" though is providing the basic "object" that the metatable is stored in. For example, say that you are using LuaJIT, and you want the class/metatable to be a LuaJIT object. This is sort of a stretch, though. For one thing, I'm not sure this LuaJIT idea could work with all the "weird" fields like If I had to make a guess as to why the inheritance "goes backwards", I'd guess that this is literally a bug. The clue is in the
That's so weirdly specific it feels like an artifact that maybe the code used to work another way. Incidentally, I was wrong about something above. I said that the first-argument table approach is undocumented, and that _base is undocumented. It's not, both of these two things are documented in the guide, I just didn't see it. (_create and _handler are still not documented, though). So you were using a supported path all along, sorry lol Anyway, you have convinced me, I think given specifying fields of your class in the table is a documented and therefore clearly intended use case when the metatable fields are given as the first argument, it's reasonable for a user to assume that the table will behave the same way when it's passed as the third argument. The inheritance going the "other way" just because you specified the base class as the first argument rather than as _base is incredibly unexpected and will probably never do anything but surprise a user and introduce bugs. I think penlight should remove |
If I understand what you're referring to, I still think this part is correct. If you haven't read it already I suggest reading this page from the lua docs, specifically the part after "The use of the __index metamethod for inheritance is so common that Lua provides a shortcut". |
Thanks for the docs link. Lua plays a bit fast and loose with the term "meta", and I see they call some things meta that I wouldn't have. That confusing point is exacerbated by the fact that that I think we're on the same page now, but this is a serious can of worms. Dropping the However, that doesn't actually fix my problem like I thought it would. It turns out my test code at the start of this issue isn't actually testing what I thought it was. Even when I got the output I expected, it wasn't actually getting there the right way. I started trying to fix that, and fell down a rabbit hole. Your changes to avoid problems with In trying to come up with tests for how I think it should work, I ran across a really weird usage in I'm headed to bed and will have to dig back in another time, but I'm posting some of the things I was messing with to a draft PR. |
For giggles, Penlight classes are their own grandpa: Line 152 in ce0bdba
On a more serious note, this line strikes me as bogus: Line 156 in ce0bdba
Deleting it fixes some cases and breaks others so obviously something needs to be done, but whatever needs to be done I don't see why it should be happening only to some alternative syntaxes but not others. This stems from 552807f, the commit message for which is not particularly enlightening. |
This sounds wrong to me, unless I'm thinking about it wrong removing Will attempt to look at the test cases and your PR later this week or weekend. |
I do think that "The 'plain table plus _base' case should act like the 'inherit from a class plus specify a table' case, especially since the documentation gives you nothing to suspect the two cases are different. |
These are two different topics. Removing the But that's not what my previous comment was about. I was referring to your PR #289 broke one thing that was working. Albeit something that was not being tested properly, but something we've roughly agreed should work above. The case I'm talking about is the plain table with a local class = require("pl.class")
local Foo = class({
_init = function(self)
print("init foo")
end
})
local Bar = class({
_base = Foo,
_init = function(self)
self:super()
print("init bar")
end
})
A = Bar() -- expect "init foo\ninitbar", get "init bar" In many other cases your work with |
Hey @mcclure maybe you can answer this. It's possible I've misunderstood how
pl.class()
is supposed to work (it is under documented after all!), but with inheritance working properl now (thanks for that!) I'm on a mission to get rid of some of the ugly hacks I had in place in various projects.I can get inheritance to work using the documented syntax (see
foo
class below and thebaz
subclass), but I was under the impression that another syntax should work too (seefoo
class andbar
subclass). Unfortunately it does nothing at all.Since that doesn't work as expected, one way I found to make subclassing convenient is using
pl.tablex.update()
to populate the class after defining it as an exact copy of the parent class (seefoo
+qiz
below). Additionally I've used another syntax (seefoo
+zar
below) that works, but creates an extra degree of separation and (as of anything later than Penlight 1.8.0) now does not have asuper()
method. I think this new behavior is correct –it shouldn't have one because there is a break in the inheritance since the constructor is given a plain table instead of a class— but still it leaves things in an awkward place.Can you comment on why
foo
+bar
doesn't work in the second example here?The output I get is:
Obviously I expected the second line to be a subclass with different properties, not just a copy of the first class.
The text was updated successfully, but these errors were encountered: