An Interesting Example of Imperative Versus Functional Thinking with Neo4j
Join the DZone community and get the full member experience.
Join For FreeOver the weekend I was trying to port some of the neo4j import code for the ThoughtWorks graph I’ve been working on to make use of the REST Batch API and I came across an interesting example of imperative vs functional thinking.
I’m using the neography gem to populate the graph and to start with I was just creating a person node and then creating an index entry for it:
people_to_load = Set.new people_to_load << { :name => "Mark Needham", :id => 1 } people_to_load << { :name => "Jenn Smith", :id => 2 } people_to_load << { :name => "Chris Ford", :id => 3 } command_index = 0 people_commands = people_to_load.inject([]) do |acc, person| acc << [:create_node, {:id => person[:id], :name => person[:name]}] acc << [:add_node_to_index, "people", "name", person[:name], "{#{command_index}}"] command_index += 2 acc end Neography::Rest.new.batch * people_commands
people_commands ends up containing the following arrays in the above example:
[ [:create_node, {:id=>"1", :name=>"Mark Needham"}], [:add_node_to_index, "people", "name", "Mark Needham", "{0}"], [:create_node, {:id=>"2", :name=>"Jenn Smith"}], [:add_node_to_index, "people", "name", "Jenn Smith", "{2}"], [:create_node, {:id=>"3", :name=>"Chris Ford"}, [:add_node_to_index, "people", "name", "Chris Ford", "{4}"] ]
We can refer to previously executed batch commands by referencing their ‘job id’ which in this case is their 0 indexed position in the list of commands. e.g. the second command which indexes me refers to the node created in ‘job id’ ’0′ i.e the first command in this batch
In the neo4j REST API we’d be able to define an arbitrary id for a command and then reference that later on but it’s not implemented that way in neography.
I thought having the ‘command_index += 2′ was a bit rubbish because it’s nothing to do with the problem I’m trying to solve so I posted to twitter to see if there was a more functional way to do this.
My colleague Chris Ford came up with a neat approach which involved using ‘each_with_index’ to work out the index positions rather than having a counter. His final version looked like this:
insert_commands = people_to_load.map do |person| [:create_node, {:id => person[:id], :name => person[:name]}] end index_commands = people_to_load.each_with_index.map do |person, index| [:add_node_to_index, "people", "name", person[:name], "{#{index}}"] end people_commands = insert_commands + index_commands
The neat thing about this solution is that Chris has separated the two concerns – creating the node and indexing it.
When I was thinking about this problem imperatively they seemed to belong together but there’s actually no reason for that to be the case and we can write simpler code by separating them.
We do iterate through the set twice but since it’s not really that big it doesn’t make too much difference. to the performance.
Published at DZone with permission of Mark Needham, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments