Efficient design for your lua library classes ?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
Roland_Yonaba
Inner party member
Posts: 1563
Joined: Tue Jun 21, 2011 6:08 pm
Location: Ouagadougou (Burkina Faso)
Contact:

Efficient design for your lua library classes ?

Post by Roland_Yonaba »

Hi all,

I would like to have some clever thoughts about good habits on making library classes, especially with Lua.
In terms of clean design and efficiency, of course.
Well, as I am not a former coder, just a random guy who's hanging with Lua, I don't have a clear opinion about what is best way to proceed. But I always considered that, when designing a class library, most of the time you 're hiding complex operations in the internals of the library source. Then, you expose a public interface to the user, with public methods.

Code: Select all

local internal_vars
-- variable declarations
local internal_funcs = ...
-- funcs declarations

-- the class itself
local libClass = {}

-- some methods
function libClass:methodA(...) ... end
function libClass:methodB(...) ... end

-- exposes the interface to the user
return libClass
I also tend to think that one should not trust (at all) to the user inputs. So, inside methods, it might be better to perform some argument checking, before processing the inputs and return the results. And I'm fine with that. Problem is, one thing I hate is, when an err occurs because invalid args were passed-in, the stack trace report the line number that failed inside the library itself. Well, to prevent that, and just report the line from the user code that caused the problem, I can use Lua's errorfunction, at level 2. Doing this in each and every single method you esecially care can end up with an ugly code... Any thoughts on this ?

Second, class attributes. You might want to define some specific attributes inside a class, and you don't want the user to be able access these attributes explicitely. But instead, you'd like to force him to use some getters/setters you provide as library methods. Actually, proxy tables can do the trick. Or at least, define these attributes as locals in the library code, as follows :

Code: Select all

local attr1 = ...
local libClass = {...}
function libClass:setAttr1(...) ... end
function libClass:getAttr1() return attr1 end
Once again, any thoughts on this ?

That would be all. For now.
Thanks reading.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Efficient design for your lua library classes ?

Post by kikito »

Regarding the first question, yes, returning a local table is the Right Thing To Do when creating a library (of classes or otherwise) in Lua. I do that for all my libraries except middleclass, and I plan to adopt that policy in middleclass 3.0 (which is the next thing on my pipeline after I finish bump 2.0).

On a sidenote, make sure that the library-level methods don't require passing "self". In other words, do this:

Code: Select all

myLib.myMethod(foo, bar, baz)
Not this:

Code: Select all

myLib:myMethod(foo, bar, baz)
I stress that this is for the functions directly attached to the library table. For the classes created with the lib using : is fine (even expected in some cases).

Regarding the second question: there're actually levels to do this. The lower level is just prepending the private attributes with underscore. This will tell your library user: "this is mine. If you use it in your code, future versions of the lib will not work. If you want to live dangerously, go ahead".

It's totally possible to create "really private" stuff. The strategy to use depends on what you want to make private, exactly: class methods, instance methods and instance attributes might be done differently. Usually, it involves creating local variables in a scope that the library user has no access to.

I actually did a writeup about all those methods (even mentioning the underscoring) in the middleclass wiki:

https://github.com/kikito/middleclass/w ... vate-stuff

I hope this helps :)
When I write def I mean function.
User avatar
Roland_Yonaba
Inner party member
Posts: 1563
Joined: Tue Jun 21, 2011 6:08 pm
Location: Ouagadougou (Burkina Faso)
Contact:

Re: Efficient design for your lua library classes ?

Post by Roland_Yonaba »

kikito wrote:I hope this helps :)
Seriously ? That made my day. Learned alot a lot, thanks!
May I mention a slight typo in that article ?

Code: Select all

print(stewie:getName()) -- stewie
stewie.name = 'ann'
print(stewie.name()) -- ann <-- this line
Shoudn't it be ?

Code: Select all

print(stewie.name) -- ann
Regarding the first question, yes, returning a local table is the Right Thing To Do when creating a library (of classes or otherwise) in Lua. I do that for all my libraries except middleclass, and I plan to adopt that policy in middleclass 3.0 (which is the next thing on my pipeline after I finish bump 2.0).
Yes, totally. But my question (and I may have not stated it correctly) was more about argument type checking, and error handling in class methods. :)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Efficient design for your lua library classes ?

Post by kikito »

Roland_Yonaba wrote:May I mention a slight typo in that article ?
Fixed, thanks (I believe that github wikis can be edited by anyone, if you find a typo in a gh wiki in the future, try the "edit" button)
Roland_Yonaba wrote:Yes, totally. But my question (and I may have not stated it correctly) was more about argument type checking, and error handling in class methods. :)
My rule for that one is simple: I check the arguments in the "main entrances" of my libraries only.

The objective for these checks is not some abstract "enforcing cohesion" goal. It's "helping the library user identify problems in his params as easily and quickly and possible". So instead of an obscure "nil reference" error, they get a message that says "a class name was expected as the first param in libClass.new, but instead [nil] was received".

I do param checks on the "main library entrances" only. Not on internal methods, since I trust my own code, and sometimes these checks just get in the way when forking/extending a lib (are you sure someone in the future will not want a negative number on this param?). I don't check params in all public methods either, just the "main" ones. Sometimes it's a matter of efficiency - checks take a small amount of time, so if a method has to be called very frequently, I might skip the tests on that one. In other cases, some public methods just invoke other methods that already do the checks, so I don't double-check stuff.
When I write def I mean function.
Santos
Party member
Posts: 384
Joined: Sat Oct 22, 2011 7:37 am

Re: Efficient design for your lua library classes ?

Post by Santos »

So Kikito, why is it exactly that library-level methods shouldn't require "self"? I think you've mentioned this before and noted that it can be localized, but I was curious to know if there are other reasons. :)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Efficient design for your lua library classes ?

Post by kikito »

Santos wrote:So Kikito, why is it exactly that library-level methods shouldn't require "self"? I think you've mentioned this before and noted that it can be localized, but I was curious to know if there are other reasons. :)
It's idiomatic. The expected thing is for them to use ".", not ":". None of the standard Lua libraries uses ':' (you can on strings, but in that case the first parameter is a string, not the string library). By requiring ":" you would be deviating from the standard for no reason; implementing the function without self (in a library method) is trivial.
When I write def I mean function.
Santos
Party member
Posts: 384
Joined: Sat Oct 22, 2011 7:37 am

Re: Efficient design for your lua library classes ?

Post by Santos »

Aah, good point, thanks! :)
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Efficient design for your lua library classes ?

Post by Inny »

I can't speak towards what makes a great library that 3rd parties are going to be using, but I can make a few points about what makes Lua code "clean" which is always a desired feature.

1. As kikito pointed out, libraries are generally considered a collection of things, and in that regard we use the dot notation rather than the colon notation for accessing that content. I do have one slight disagreement with this policy, and that's on the use of singletons. If you're designing a library that's going to act in the capacity of a singleton, then you may want to consider the colon notation, to indicate that it's a very stateful thing that's being used. But you should totally avoid singletons, especially with 3rdparty libraries. To put it simply: your "Util" library containing unassociated functions, use the dot.

2. Limit scope of variables. The number of locals per scope are a fixed amount, and I forget the amount off hand, but it's always a good idea to keep variables local to the scope they're used in. On this point, the do...end scope keywords are your best friend. So if one particular function in your library is going to have a large closure around it, encase it in a do end block so that other functions don't have to share that scope.

3. Separation of concerns. This is a larger software engineering principle, but make sure your functions are really only doing one thing. If you see your function is twiddling bits and poking your widgets, they're probably better off as two functions.

4. Decide on Mechanism or Policy. Is your library provided a core mechanism? If so then you want it to really just be a collection of functions that return whatever their results are and shies away from keeping an internal state. Just look at how love's own built-in libraries work: The functions are designed to take as input the output of other functions. This contrasts with something like a Widget library, which is all about policy and is really just wrapping some other library's mechanisms.

5. Make efforts toward self-documentation. The difference between a variables named x1, x2, and x3 aren't apparent, but you know exactly what I mean when I say xStart, xEnd, and xStep. The commenting operator -- is all but missing from most Lua code because we actually don't need it, because we can do such a good job at naming things correctly.

6. Avoid "Code Smell", meaning sometimes code has to get a little messy, but don't give up on the whole file and allow it to get messy everywhere. Too many levels of indentation or too many vague variable names can really ruin code. To put it another way, code isn't done when it works, it's done when you've had a chance to rewrite it to make it look pretty and it still works.

7. Test. Test test test. Test test test test test. You can't know your code works until you've run it through a test. So if you write a library named foo.lua, you also have to write a test harness named testfoo.lua. It doesn't need to be complex, it can just be a list of assert statements that prove the output of your functions are what you say they are.

I'll leave it there, but there are tons more principles that you can read up on, of which my favorites are Eric Raymond's list: http://en.wikipedia.org/wiki/Unix_philo ... ic_Raymond
Post Reply

Who is online

Users browsing this forum: No registered users and 4 guests