Path is: i h g d a
distance is 6
Path is: k - 1 - j - 1 - c - 3 - b - 2 - a
distance is 7
In my opinion there are some significant issues with the ruby example on the fulloo site. This mostly results out of the use of injection and #extend. In the interests of matching the behaviour of the code on site I have duplicated this behaviour. However I feel some of it runs contrary to what is now accepted DCI.
Here are my detailed notes:
1) The first issue is that the example on the website uses the same role behaviour for multiple objects at the same time. I.e. anonymous roles. This means I had to add this feature to HybridDCI.
On line 183 ....
|node| node.extend Distance_labeled_graph_node
This assigns the same role behaviour "Distance_labeled_graph_node" to many role-players. This is done so that each node can call methods such as #set_tentative_distance_to etc.
To mimic this I need a way to call these role methods. This is simple when using injection and #extend because these methods are called first, however this is not true to DCI principles because those role methods will always be available on the object inside and outside of the context.
In previous posts by Rune, he says that a role method may be called by going through the role explicitly i..e RoleA.foo, or by self.foo within the context of a role's role methods. As roles are not named the only way to trigger a role method (other than injection) is via a method on the role_player itself. Therefore to implement this I had to add the method #as_role to role players, this mimics injection and enables a role_method to be called even though the call does not go explicitly though a role.
2) On lines 391 - 394, nodes inside an "unvisited_neighbours" array are iterated over. These nodes may either be a south_neighbor role or a east_neighbor role. Regardless which role, the method #relable_node_as is sent to to these objects.
This is a role method that is sent to an object without explicit reference to the role.
unvisited_neighbors.each {
|neighbor|
net_distance = current.tentative_distance + map.distance_between(current, neighbor)
if neighbor.relable_node_as(net_distance) == :distance_was_udated
pathTo[neighbor] = current
The problem with this is that it is only by chance that this algorithm works. It works because the #relable_node_as method contains the same code for both the south role and the east role. If #relable_node_as was different for the south and east roles, the wrong method may be picked for two reasons.
(a) #extend does not re-order included modules, so #extend Moo; #extend Foo; #extend Moo, would never call Moo's role methods over Foo in the event of a clash, even though Moo was the last extended module. This is because Moo was already extended first and repeated extensions do not have any effect. So extend SouthNeighbor; extend EastNeighbor; extend SouthNeighbor. will never call SouthNeighbor's #relable_node_as method. We are just fortunate these methods contain the same logic.
(b) Given we are not accessing the role method explicitly though a role, how do we know which role & role method to use? It just so happens that these role_players have only one role assigned. Therefore we can choose that role's method. This is a by-product of injection only. If those objects had multiple roles, again, which one to choose?
In order to copy the behaviour of the code on site, I choose to duplicate injection behaviour. However my preferred solution would be to always have to explicitly call a role method via it's role.
If we want to allow role methods to be called outside of an explicit reference to a role, then we need to decide what order to check the existing roles in the case of multiple roles.
3) On lines 273, inside the #relable_node_as method, on the EastNeighbor role. The method self.tentative_distance is called. Now tentative_distance is not a role method of EastNeighbor, nor is it an instance method. It is a role method of another role entirely. It is a by-product of injection that the correct role-method gets called.
In the context of DCI how can we determine which role 's role method to call. Rune suggests in another post that this type of call should only call an instance method, and I agree. It is very confusing to try to figure out what role method is going to be called as there is no explicit reference to the role. That said, in order to mimic the code on site, I do a search on all existing roles applied to an object and select the last one that responds to that role method. This mimics injection.
I will remove this feature though, it's only present in order to match the fullinfo example.
4) In the code on site, roles and role methods are referenced outside of a context. On line 700 geometries.distance_between is called, outside of any context.
The only reason this works is because earlier within a context (on line 476) geometries was extended within this method. This is role behaviour leakage. The way I solved this was by moving the calculation back into a context, and calling that context explicitly to prevent role method leakage.
======
Thats generally it, other than that everything else was a fairly direct copy of the code on site.
Im not ready to release the source to this HybridDCI just yet, as I want to resolve the issues (1 -3 ) above and improve performance first.