Thread Tools Display Modes
05/19/14, 03:06 AM   #1
zgrssd
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 280
Post The getset (gs) pattern

Ever since I started learning the WPF GUI System for .NET I have become a big friend of having get and set functions instead of directly exposed fields. In .NET get&set have been combined into the (almost entirely synrax sugar) construct of properties wich make setting them up a breeze.
They allow stuff like Validation and Change Notification to be in one place (wich in turn really simplifies GUI programming). The only thing you have to keep in mind are to never write the backing field directly (especially in the code that contains the property/get & set).

Latest when you work with LibAddonMenu you need a getter and setter too. So my first ones looked like this:
Code:
--Version 1, conventional get and set
local function getSomeValue()
    return SomeValue
end

local function setSomeValue(value)
    SomeValue = value
end
The I realised that I might be able to fold both abilites into a single function in lua, a getsetter or "gs":
Code:
--Version 2
local function gsSomeValue(value)
    if (value ~= nil) then
        SomeValue = value
    else
        return SomeValue
    end
end
As paramters are filled with up with nil if nothing is specified "local value = SomeValue()" would get me the value while "SomeValue(value)" would set it.
This has one disadvantage though: nil values are impossible to set anymore (wich may be a good or bad thing, depending on case).

After some thinking I realised that with Input Validation a immediate check of the real value would be needer after every set. So I put the return outside of the if to avoid having to make an extra function call:
Code:
--Version 3
local function gsSomeValue(value)
    if (value ~= nil) then
        SomeValue = value
    end
    return SomeValue
end
Wich can be used like
Code:
local value = --some String input that has to be parsed in the setter
value = gsSomeValue(value)
Version 2 and 3 work well with LibAddonMenu (you just drop the same reference into both get and set field). But I have not checked it with all Elements that need the pair (if some of them try to set nil it would break down).

Last edited by zgrssd : 05/19/14 at 03:09 AM.
  Reply With Quote
05/19/14, 03:40 AM   #2
Harven
 
Harven's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 135
Hey,
I do like to use getters and setters while writing code in objective launguages which offer some kind of encapsulation. But I don't use them in lua because I think it's just a syntax sugar and accessing a variable directly is faster than calling a function. In launguages such as c++ compiler can optimize such functions calls but in lua the function is called every time so it costs more. But that is just my opinion
  Reply With Quote
05/19/14, 01:22 PM   #3
katkat42
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 155
Yeah, I too like getters and setters in object-oriented languages, and when forcing procedural languages to be object-oriented (PHP anyone?). I find they add clarity in the latter situations. So I use them some places in lua an not in others.
  Reply With Quote
05/20/14, 05:03 AM   #4
zgrssd
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 280
In Lua you are either forced to use Local variables (wich is like Private in other languages) or risk colliding with other peoples code in the Global Variable Space.
I use this Namespace pattern to explictly expose stuff that should be global:
Code:
--A bunch of local functions

MyAddonNamespace = {
    foo = foo, --assign local fucntion to an entry on the Global table variable
    gsXIsEnabeled = gsIsEnabeled --assing getsetter to this public entry
}

--Some say this extra return is good practice, so I just keep it in
return MyAddonNamespace
But it only covers functions and having a get and set function would be a lot of extra work. With the getset pattern I can explictily expose those fields in a single entry (closer to how properties work).

Speed is a consideration, but among the first things I had to accept as programmer is that sometimes ease of use and productivity takes precedence over squeezing out the last ounce of speed.
Sure it is slower, but it is still fast enough to be used. See part 2 here: http://ericlippert.com/2012/12/17/performance-rant/
  Reply With Quote
05/20/14, 10:27 AM   #5
Wobin
 
Wobin's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 78
Code:
local value = --some String input that has to be parsed in the setter
value = gsSomeValue(value)
I'm sorry, but this ... just isn't clear at all. I would rate code readability much more highly over unnecessary encapsulation. What does it do? Do I have to trace back into the function to get a sense of just what's happening to a string as a -side effect- of a get/setter? Why are we setting it back to the local value?

If I came across this function in the wild, it would make no sense at all. Ok, I can understand, to a certain degree, encapsulation on a theoretical level, but if it overcomplicates the entire process, it's not actually helping, only hindering.

How is this in any way beneficial to the system? How is this easier to use than stripping it down to simple basics?
  Reply With Quote
05/20/14, 12:03 PM   #6
zgrssd
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 280
Originally Posted by Wobin View Post
Code:
local value = --some String input that has to be parsed in the setter
value = gsSomeValue(value)
I'm sorry, but this ... just isn't clear at all. I would rate code readability much more highly over unnecessary encapsulation. What does it do? Do I have to trace back into the function to get a sense of just what's happening to a string as a -side effect- of a get/setter? Why are we setting it back to the local value?

If I came across this function in the wild, it would make no sense at all. Ok, I can understand, to a certain degree, encapsulation on a theoretical level, but if it overcomplicates the entire process, it's not actually helping, only hindering.

How is this in any way beneficial to the system? How is this easier to use than stripping it down to simple basics?
A centralised place to make certain only valid values are set is one of the core uses for setters. If everyone uses the setter, nobody can put in faulty stuff. Preventing the programmer from doing stuff he is not supposed to do is about 90% of the reason to even have classes and limtied visibility/scope. Or generics. Or types for that mater.
If you set a value you know might be reinterpreted you have to check what the values is after the tried assignment. I just cut out a extra call to getSomeValue() or gsSomeValue by always letting the getsetter return the value and storing it directly (so it can be re-exposed to the UI).
  Reply With Quote
05/20/14, 05:51 PM   #7
Wobin
 
Wobin's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 78
Ah, I see.

Well, if preventing the programmer from being able to access/adjust values as they see fit was the aim, then 90% of the addons on this site would not work =) If every addon had perfectly hidden and controlled values, we'd never be able to change stuff we like.

Having that sort of control is well and good if you -want- perfect and inviolable control over the UI.

Thankfully ZO isn't quite that shortsighted. Leaving values available to be adjusted means that they can offer an API they don't have to work too much on, and meanwhile everyone else is happy, because they can change the things they want.

I suppose it's more of an environmental consideration for me.

At work, sure. Keep everything encapsulated, set aside, locked off from 'silly programmers'.

In game? If they want to change stuff, let them. If they want to screw up setting a value? Go right ahead. It's a learning experience then, when the users come to them and say "Your addon is clashing with this one", and they go to see what went wrong.

Many people are only just starting to learn how to code here. Preventing them from making simple mistakes where it just -doesn't matter- isn't helping them learn. Giving them a sandbox to play in where doing something wrong isn't causing real life downtime, or costing a company valuable money, is important imo, as it gives them the scope to try things out without serious consequence.

They can learn the serious stuff later if they ever take their skills into the professional arena. But until then, this is still a game =)
  Reply With Quote
05/28/14, 03:31 AM   #8
zgrssd
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 280
My ultimate goal was to fully copy the functionality of the MVVM pattern to Lua. Having getter and setter folded into on function was the first part of it. Events was the second.
You see, most UI design patterns ask "How do I inform the UI of changes to my values", wich involves taking a reference to the UI. And also means that a different UI will be hard to do.
MVVM says "That is a stupid question. I don't care for the design of the UI." Instead it says: "I only expose properties. I have change notification on those properties. Everyone who has a stake in the value of the property, can just register the change event. I do not need to know squat about the UI this way, meaning that any UI can work with my backend."

Code:
--Create your local CallbackManager and a helper function to raise the events
local LocalCallbackManager = ZO_CallbackObject:Subclass()
--Backing variable for gsSomeValue, you might use a Setting Accessor Object or something similar as backing field instead. A string indexed table might be a good place to hold them while also allowing simplyfying the gs code.
local _SomeValue = nil 

local function RaiseGSChanged(gsName, newValue)
	LocalCallbackManager:FireCallbacks(gsName .. "Changed", newValue)
end


local function gsSomeValue(value)
	if(value ~= nil and value ~= _SomeValue)
		_SomeValue = value
		 RaiseGSChanged("gsSomeValue", _SomeValue)
	end
	return _SomeValue
end

--think a dozen more gs, all using this pattern being written here

--Expose local CallbackManager and the getsetters
ChangeNofiticationExample = { 
	gsSomeValue = gsSomeValue
	CallbackManager = LocalCallbackManager
}
"Let however has a stake in the value of gsSomeValue, if he registered the callback, be informed of changes."
It just became unessesary to do expensive polling to get changes of the value. You register the event. You get informed. No mater where the change came from (as long as the original programmer always used the gs in his code too). No mater if you are in the same code, or code in a totally different addon. Or if you were written 10 years after the original addon.
  Reply With Quote
05/28/14, 04:28 AM   #9
Sasky
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 231
The main intent of the "Does it need to be optimised?" part is you don't go making your code more complex (and error-prone) to get a little bit of optimization. What do you gain by combining your get/set functions besides making it needlessly complex? KISS - Keep it simple stupid.

Even though you don't get the full read-only guarantees on getters like you can in C++, it makes your API clearer. You can easily say "The get function has no side effects."

For example, what happens if you want to actually use NIL as a value? There is no way to signal this to your getset combined function. What's worse is it will happily return a value silently and proceed on its way with no warnings. If you get a NIL passed in, you'll only see the results later of a NIL not being stored (not in a clean error message) and it could be a difficult bug to notice and track down.

(PS: Doesn't mean you should abandon a MVVM pattern, just keep it with traditional get/set functions.)
  Reply With Quote
05/29/14, 03:51 AM   #10
zamalek
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 9
This is quite common in Javascript (JQuery uses it a lot). Not a big fan of "gs" as it's a bit redundant - if you want to allow people to set properties to nil you can use varargs (which, again, is how you generally use this pattern in Javascript):

Code:
function SomeValue(...)
    if arg.n == 1 then _someValue = arg[1] end
    return _someValue
end
  Reply With Quote
05/29/14, 04:28 AM   #11
Garkin
 
Garkin's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2014
Posts: 832
Originally Posted by zamalek View Post
This is quite common in Javascript (JQuery uses it a lot). Not a big fan of "gs" as it's a bit redundant - if you want to allow people to set properties to nil you can use varargs (which, again, is how you generally use this pattern in Javascript):

Code:
function SomeValue(...)
    if arg.n == 1 then _someValue = arg[1] end
    return _someValue
end
The use of arg is deprecated (since Lua 5.1). I don't think that it will work in game.

If you want the same functionality, you have to define arg first:
Lua Code:
  1. function SomeValue(...)
  2.    local arg = { n = select("#", ...), ... }
  3.    if arg.n == 1 then _someValue = arg[1] end
  4.    return _someValue
  5. end
  Reply With Quote
05/29/14, 04:51 AM   #12
zamalek
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 9
Gotta love those Lua docs! So, in the spirit of DRY (donot repeat yourself):

Code:
local function Property(get, set)
    local prop = function(...)
        if select('#', ...) == 1 then set(({...})[1]) end
        return get()
    end
    return prop
end

local someValue
local SomeValue = Property(
    function() return someValue end,
    function(value) someValue = value end
)

SomeValue(1)
print(SomeValue())
I think we may be overthinking this...

Last edited by zamalek : 05/29/14 at 05:05 AM.
  Reply With Quote
07/01/14, 05:22 AM   #13
zgrssd
AddOn Author - Click to view addons
Join Date: May 2014
Posts: 280
getSet Pattern Mk.2 - now with Metatables.
Finally got around to properly reading into thier basics. With Metatables I am able to set up functions to be called in case a index is not set(metamethod __index).
I can also set up a function to do work if somebody tries to set a index that does not exist yet (metamethod __newindex)
With those two I can get a lot closer to simulating Properties in Lua, while even retaining all the syntax sugar of them (that they can be accessed like other fields). I just redirect every get and set attempts to my functions, preventing the table value from ever being set.

Code:
--accessing a index that was never defined reading
Instance["SomeValue"]
--Is interpreted like this with a metamethod around:
(getMetatable(Instance).)__index(Instance, "SomeValue")
Code:
--trying to write a index that currently has no value
Instance["SomeValue"] = "Hello World"
--Is interpreted like this with a metamethod around:
(getMetatable(Instance).)__newindex(Instance, "SomeValue", "Hello World")
I still need to figure out how to hide the backing fields from external manipulation, but I got some ideas already (like preventing the metatable from being set or seen and just puttig it there)

Of course the whole thing can be circumvented by getting the raw value (while ignoring metamtehods). But if somebody tries that, he or she better knows what the heck they are doing there.
  Reply With Quote
07/01/14, 09:15 AM   #14
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
While I generally agree with Wobin, I thought your quest (the part with restricting variable access) would be a good Lua excersise (for me at least ). So here's my shot on how you can completely hide the stored values from anything but get/set functions.
Lua Code:
  1. local function setNumber(tab, key, val)
  2.     local nval = tonumber(val)
  3.     if not nval then
  4.         error("attempt to assign non-numeric value '" .. tostring(val) .. "' to '" .. key .. "'")
  5.     end
  6.     rawset(tab, key, nval)
  7.     return nval
  8. end
  9.  
  10. local function setString(tab, key, val)
  11.     rawset(tab, key, tostring(val))
  12. end
  13.  
  14. function gsNumberProperty()
  15.     return rawget, setNumber
  16. end
  17.  
  18. function gsStringProperty()
  19.     return rawget, setString
  20. end
  21.  
  22. function gsPropertyTableConstructor()
  23.     local getters = {} -- ["name"] = function get(t, k)
  24.     local setters = {} -- ["name"] = function set(t, k, v)
  25.     local ctor = {
  26.         defProperty = function(name, get, set)
  27.             getters[name] = get
  28.             setters[name] = set
  29.         end,
  30.         newPropertyTable = function()
  31.             local values = {}  -- the 't' passed to get/set
  32.             local mt = {
  33.                 __index = function(_, key)
  34.                     local getter = getters[key]
  35.                     if not getter then
  36.                         error("attempt to access undefined property '" .. key .. "'")
  37.                     end
  38.                     return getter(values, key)
  39.                 end,
  40.                 __newindex = function(_, key, val)
  41.                     local setter = setters[key]
  42.                     if not setter then
  43.                         error("attempt to assign undefined property '" .. key .. "'")
  44.                     end
  45.                     -- the value must not be stored in the first argument,
  46.                     -- otherwise __index/__newindex would stop being called
  47.                     return setter(values, key, val)
  48.                 end,
  49.             }
  50.             -- the returned table must remain empty
  51.             return setmetatable({}, mt)
  52.         end,
  53.     }
  54.     return ctor
  55. end

And here's how you'd use it:
Lua Code:
  1. -- create a constructor
  2. local gs = gsPropertyTableConstructor()  
  3. -- define some properties
  4. gs.defProperty("foo", gsStringProperty())
  5. gs.defProperty("bar", gsNumberProperty())
  6. -- create a table
  7. local props = gs.newPropertyTable()
  8. -- set properties
  9. props.foo = "hello"
  10. props.foo = props.foo .. " world"
  11. props.bar = 5
  12. props.bar = "no way!" -- will fail
  13. props.miss = 1 -- fail
  14. d(props.miss) -- fail
Of course the moment someone uses rawset(props, ...), it will stop working.
  Reply With Quote
07/01/14, 01:45 PM   #15
Sasky
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 231
For a more in-depth discussion on the metatables approach, take a look at:
http://lua-users.org/wiki/ReadOnlyTables
http://lua-users.org/wiki/ObjectProperties
  Reply With Quote

ESOUI » Developer Discussions » General Authoring Discussion » The getset (gs) pattern


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off