How Ruby Taught Me To DRY Up My Code With Lambda Blocks
I’ve been working in Ruby for my Albacore project over the last 6 or 8 months, and taking every chance I can find to learn how to really use the language effectively. One of the benefits I’m seeing in a dynamic language like Ruby is the ability to really DRY up your code through it’s dynamic/duck type system, and through metaprogramming.
I’ve noticed in my ruby code that I tend to see repeated patterns of implementation in a different light. Rather than seeing the things that make each repetition of the pattern different, I tend to see the things that make each repetition of the pattern the same. I notice the same structure used with different variable name, the same method calls used with different parameters, and context specific method calls as the outliers that made me duplicate the code in the first place. When I see these patterns, my mind begins to run down the path of “this code is duplicated… how can I eliminate that duplication?” Whereas in C#, I almost immediately see the differences as “these are different calls based on the context and I can’t eliminate this repeated pattern of code because of the unique calls each has to make.”
I’m not sure why my mind has been operating this way with C#, but I know that is has been doing this for a very long time. I’ve often written the same pattern of code 6 or 8 times, or more in some cases – especially when it comes to UI code and event handlers from UI controls. I wrote a prime example of repeated patterns in C# just today, on a UI that has 4 ComboBox controls on it. Each combobox has a SelectedIndexChanged event handler that gets the selected value and pushes it to the presenter via a presenter method that is specific to the value being pushed. Here’s the code in all it’s glorious duplication:
1: void cboTypes_SelectedIndexChanged(object sender, EventArgs e)
2: {
3: TeardownComboBoxEvents();
4:
5: var lookup = cboTypes.SelectedItem as Lookup;
6: if (lookup != null)
7: {
8: _presenter.TypeSelected(lookup);
9: }
10:
11: SetupComboBoxEvents();
12: }
13:
14: void cboGroups_SelectedIndexChanged(object sender, EventArgs e)
15: {
16: TeardownComboBoxEvents();
17:
18: var lookup = cboGroups.SelectedItem as Lookup;
19: if (lookup != null)
20: {
21: _presenter.GroupSelected(lookup);
22: }
23:
24: SetupComboBoxEvents();
25: }
26:
27: void cboCategories_SelectedIndexChanged(object sender, EventArgs e)
28: {
29: TeardownComboBoxEvents();
30:
31: var lookup = cboCategories.SelectedItem as Lookup;
32: if (lookup != null)
33: {
34: _presenter.CategorySelected(lookup);
35: }
36:
37: SetupComboBoxEvents();
38: }
39:
40: void cboCodes_SelectedIndexChanged(object sender, EventArgs e)
41: {
42: TeardownComboBoxEvents();
43:
44: var lookup = cboCodes.SelectedItem as Lookup;
45: if (lookup != null)
46: {
47: _presenter.CodeSelected(lookup);
48: }
49:
50: SetupComboBoxEvents();
51: }
</div> </div>
When I wrote this code and looked back at it, I had my usual feeling of “well, these presenter calls are specific the the context of the combox being selected, so I can’t really do anything to eliminate this repeated pattern of code.” I even went so far as to think “man, if this were Ruby, I wouldn’t have any issue killing this repeated pattern.” That’s when a little voice in the back of my head started shouting at me and I realized that I could eliminate the duplication in C# just as easily as I could in Ruby with the use of anonymous delegates.
Method Blocks And Anonymous Delegates
One of the techniques I often use in Ruby to help dry up repeated code is ruby’s method blocks – basically an anonymous delegate in C#. These two code samples are functionality equivalent…
Ruby Method Block With Named Parameter
1: def my_method(&block)
2: name = "derick"
3: block.call(name) unless block.nil?
4: end
5:
6: my_method do |name|
7: puts "the name is: #{name}"
8: end
</div> </div>
C# Anonymous Delegate (Lambda) With Named Parameter
1: public void MyMethod(Action<string> block)
2: {
3: string name = "derick";
4: if (block != null)
5: block(name);
6: }
7:
8: MyMethod(name => {
9: Console.WriteLine("the name is: " + name);
10: });
</div> </div>
Eliminating This Repeated Pattern
After I finally decided to listen to that little voice shouting at me and use my tools to their full extent, I rewrote the event handlers into the following code, using an Action delegate and anonymous lambda block to execute the context specific presenter calls.
1: private void LookupSelected(ComboBox comboBox, Action<Lookup> presenterCall)
2: {
3: TeardownComboBoxEvents();
4:
5: var lookup = comboBox.SelectedItem as Lookup;
6: if (lookup != null)
7: {
8: presenterCall(lookup);
9: }
10:
11: SetupComboBoxEvents();
12: }
13:
14: void cboTypes_SelectedIndexChanged(object sender, EventArgs e)
15: {
16: LookupSelected(cboTypes, l => _presenter.AssetTypeSelected(l));
17: }
18:
19: void cboGroups_SelectedIndexChanged(object sender, EventArgs e)
20: {
21: LookupSelected(cboGroups, l => _presenter.GroupSelected(l));
22: }
23:
24: void cboCategories_SelectedIndexChanged(object sender, EventArgs e)
25: {
26: LookupSelected(cboCategories, l => _presenter.CategorySelected(l));
27: }
28:
29: void cboGroups_SelectedIndexChanged(object sender, EventArgs e)
30: {
31: LookupSelected(cboGroups, l => _presenter.GroupSelected(l));
32: }
</div> </div>
Lessons Learned
This certainly isn’t anything extraordinary, mind you. I’ve written methods with delegates and lambda blocks more often than I can remember. This code is not complex, it’s not difficult to write, it’s not difficult to read or understand. But that’s the beauty of it. It’s simple, elegant, and eliminates the repeated pattern that I was creating. There are probably some additional tweaks I could make, honestly, but I also want to keep in mind the readability and understandability of the code – not just how often a pattern is repeated.
The significance of this is not in the code that I wound up writing, but in how I came to that decision. My exposure to ruby and my predisposition to see repeated patterns of code in ruby as duplication that should be eliminated finally made a jump across the neuro-pathways of my brain into C# land. I was able to take a paradigm from a different language and different set of optimizations and capabilities, and redefine my own understanding of the current paradigms and capabilities of this situation. That kind of cross-breading and transfer of knowledge is critical to our ability to come up with new and creative solutions in situations where we believe we already have mastery.
Do yourself a favor – learn a new paradigm of development or whatever your job entails. You’ll never truly be able to say “use the right tool for the job” unless you actually know how to use the tools available, and you never know when the paradigms of one tool will cross the boundaries of your experience and begin to show you new solutions to existing problems.