Notifier passed to observable?

Shouldn’t notifiers call the observing function with themselves as an argument?

see: the ResponseHandlers’s update method’s first argument

Why am I asking?

I’m developing a tool that dynamically adds and removes views. The remove functionality is a button of the view that is to be removed. The notifier of that button is a shared function between all views dynamically added in this way. The released notifier does not allow for any arguments passed through and does not pass any notification arguments or itself (the button). So there is no way for the function to identify what view it is supposed to remove (ie. what view called the function).

If the button would be passed to the observing function I could construct some functionality that would identify it’s parent view and remove it.

My current workaround is creating the function within the private scope of the new dynamic view. So each dynamically created view allocates a new private function that can access the view from the newly defined scope.
Code should help explain:

  
local static_top_view = vb:column { }  
  
function new_dynamic_view()  
 local dynamic_view = nil  
  
 local private_remove_function = function()  
 static_top_view:remove_view(dynamic_view)  
 end  
  
 dynamic_view = vb:column {  
 vb:button { released = private_remove_function }  
 }  
  
 return dynamic_view  
end  
  
static_top_view:add_view(new_dynamic_view())  
static_top_view:add_view(new_dynamic_view())  
static_top_view:add_view(new_dynamic_view())  
  

The solution below could perhaps be of help here.
It creates dynamic functions which each get their own notifier, this way you can have more or less automatic tracking of which option does what. In this case it regards mutliple devices of which it is not known which device is actually controlled yet, but in your case you probably can apply it to buttons as well.

  
function set_pitch_device_notifiers()  
 --all available pitch-devices are getting a notifier  
 --We are generating dynamic functions that are given a notifier.  
 --This is pretty much how you can deal with dynamic functionality  
 --No need for _G['funcname_ref'] but it is practically the same.  
 --Moving and erasing or adding tracks is covered by this function.  
 local song = renoise.song()  
  
 --Let's clean up first to prevent the notifier array from getting too clunky  
 for x = 1, #song.tracks do  
 if song.tracks[x].devices_observable:has_notifier(set_pitch_device_notifiers) then  
 song.tracks[x].devices_observable:remove_notifier(set_pitch_device_notifiers)  
 end   
  
 for y = 1, #song.tracks[x].devices do  
 local func_ref = 't'..tostring(x)..'d'..tostring(y)  
  
 if device_notifiers[func_ref] ~= nil then  
 --We'll start by removing the old notifier  
 if song.tracks[x].devices[y].parameters[1].value_observable:has_notifier(device_notifiers[func_ref]) then  
 song.tracks[x].devices[y].parameters[1].value_observable:remove_notifier(device_notifiers[func_ref])  
 end  
 end  
 end  
 end  
  
 --then we simply swat the array  
 device_notifiers = {}  
  
 --populating the new notifier functions  
 for x = 1, #song.tracks do  
 --Cover DSP changes   
 if not song.tracks[x].devices_observable:has_notifier(set_pitch_device_notifiers) then  
 song.tracks[x].devices_observable:add_notifier(set_pitch_device_notifiers)  
 end   
  
 for y = 1, #song.tracks[x].devices do  
 local func_ref = 't'..tostring(x)..'d'..tostring(y)  
  
 if string.find(string.lower(song.tracks[x].devices[y].display_name), "pitch device") ~= nil then  
 --Putting functions inside an array this way rather than creating a global name-space variable  
 --has a few advantages:  
 --all functions are referenced from the same spot and the table can be simply erased ![;)](https://files.renoise.com/forum/emoticons/default/wink.gif)  
 device_notifiers[func_ref] = function()  
 change_from_tool = true   
 triggered_track = x  
 triggered_device = y  
 get_pitch_device_properties(song.tracks[x].devices[y])  
 set_pitchbend_slider (x,y, selected_instrument, sample_transpose_mode, pitch_range )  
 change_from_tool = false   
 end  
  
 if not song.tracks[x].devices[y].parameters[1].value_observable:has_notifier(device_notifiers[func_ref]) then  
 song.tracks[x].devices[y].parameters[1].value_observable:add_notifier(device_notifiers[func_ref])  
 end  
 end  
 end  
 end  
 set_pitch_device_notifier()  
 end  

Nice input vV.

This is the same problem: You need to define the function within the scope of where it is added (ig. within the x and y for loops in your case).

The solution I presented is basically the same as the solution you are presenting. The only difference is that the button in my case will remove it’s notifier when the button itself is removed, so my case does not need to manage all the allocated functions in a list.

The problems with both these cases are:
a: You can not define the observing function (device_notifiers or private_remove_function) out of scope from where it is added. Because it uses private variables within that scope (x and y or dynamic_view) to be dynamic.
b: Due to these scope related issues we are reallocating the same function multiple times within the memory. So memory consumption is multiplied by the times we need to allocate the function.

Very interesting example vV, thanks for contributing.

p.s. I think there is an extra end in the last line of your example.

Yes, your solution looked similar to what i posted, but indeed the fact that your variables are all local so you can’t grasp them outside the scope of your function makes such function quite useless when called, because all the variables are still called the same.

You could perhaps also solve the puzzle like this if you need something that should be reachable from the global scope yet have something recognisable within the local scope:

device_notifiers[z].id=‘t’…tostring(x)…‘d’…tostring(y)
device_notifiers[z].function= function() local id=‘t’…tostring(x)…‘d’…tostring(y) blahblahblah end

The id creation within the specific dynamic function is particulary the key here, because if you already have that in a function, you already know to what your function is actually refering to (which is the actual alternative for the notifier reporting itself), it just is a matter of thinking out a clever structure for that id that you can unravel (regex filtering) in your own function and handle the proces (whatever that is that you want to do).
Just remember that x and y are generic figures here, that can be generated anywhere else because they are part of a “defined” structure with a dynamic characteristic.

Well, I agree that clever id structure can solve a lot, I mean your code and solution is very good. But that’s not my point for the question.

This is true, and this is how we are solving the issue.

But my point is that when we make an id for functions we put in a list that are “all the same” or declare our function private we’re introducing redundancy.

Passing the notifier is a part of the observable pattern to reduce redundancy.

Putting things required in global scope is no problem. But we will always need to construct a function in private scope to pass on the private variables, this holds true to both our cases. Even though your case sets the private function accessible globally by adding it to a list, the function is constructed in private scope.

Maybe I should have written my example a bit more extensive to show the redundancy:

  
local static_top_view = vb:column { }  
  
function global_remove_function(sender)  
 static_top_view:remove(sender)  
end  
  
function new_dynamic_view()  
 local dynamic_view = nil  
  
 local private_remove_function = function()  
 global_remove_function(dynamic_view)  
 end  
  
 dynamic_view = vb:button { released = private_remove_function }  
  
 return dynamic_view  
end  
  
static_top_view:add_view(new_dynamic_view())  
static_top_view:add_view(new_dynamic_view())  
static_top_view:add_view(new_dynamic_view())  
  

If the sender would be passed by the notifier we would see something like this:

  
local static_top_view = vb:column { }  
  
function global_remove_function(sender)  
 static_top_view:remove(sender)  
end  
  
function new_dynamic_view()  
 return vb:button { released = global_remove_function }  
end  
  
static_top_view:add_view(new_dynamic_view())  
static_top_view:add_view(new_dynamic_view())  
static_top_view:add_view(new_dynamic_view())  
  

Some redundancy seems to be hard to avoid with viewbuilder events, but as you have clearly demonstrated the workarounds are simple.

All Renoise API properties that are _observable behave in accordance with the observer pattern as they allow an object reference (see the document API)
I believe that viewbuilder API events are based on Document observables, but without an object reference?

Redundancy is hard, it’s a big problem in information theory. What we are specifically dealing with here is mutual information: Mutual information - Wikipedia

My goal was to demonstrate that the workarounds are complex and require extra memory. Although I try to keep my examples as simple as possible to ease understanding of the problem.

The object reference is the optional argument for notifiers. It allows the notifier to pass on relative information regarding what was updated within the notifier’s object. This argument can not be the same as the argument that passes the notifier’s object itself.

Take a look at Observer pattern - Wikipedia

  1. If we look at the ResponseHandler’s update method’s second argument;
    We see it receives the information of the update and this information is printed out. This is the object reference you pointed out in the renoise docs.

  2. If we look at the EventSource’s run method’s call to notifyObservers;
    We see it passes one argument with data. This data will however be the second argument of the update method. This is because the observable pattern passes on the calling object (EventSource) as the first argument.

  3. If we look at the ResponseHandler’s update method’s first argument;
    We see the argument that holds the calling object. This object is never used in the wikipedia example as it is only there for dynamic use. We should however note that notifyObservers(response) call only passes the data, a single argument. However the ResponseHandler receives two arguments, the first one being added by the Observable pattern.

Renoise is using the observable pattern, that’s for sure. But I think it’s implementation is missing an argument.

I agree that it wouldn’t hurt if there could be added some extra arbitrary parameters that will be fed as arguments for the function.

Well I’ll also agree that it wouldn’t hurt to have a third argument for arbitrary data. But that’s not my point with the question, and I would consider such an argument more of a feature/wishlist request. An arbitrary argument would also not solve our case studies as we would still need to maintain references to all possible callers and pass them along correctly with their calls.

The argument missing is the caller/sender of the notification.

I found some examples of observer patterns in LUA to further explain:

http://lua-users.org/wiki/ObserverPattern

In this example the caller is the subject argument.

http://lua-users.org/wiki/AlternativeObserverPattern

In this example the caller is the self argument.