Sunday, January 25, 2015
Seven Languages in Seven Weeks Ruby Day 2
Ruby, Day 2: Thoughts
I originally learned Ruby (and many other programming languages) the "hacker way": that is, I did a 10 minute syntax tutorial, browsed other peoples code a bit, and then just started using the language, looking up missing pieces as I went. Although this is the most fun and productive way Ive found to get started with a language, it can also lead to missing some of the finer points and subtleties.
For example, until the "Ruby, Day 2" chapter, I never had a full appreciation for Ruby code blocks and the yield keyword. For example, even though I frequently used "times" to do looping, I never thought deeply about how it worked:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10.times { puts "Jim" } |
It turns out that times is just a function (slightly obscured because Ruby doesnt require parentheses for function calls) on the Integer class that takes a code block as an argument. Times could be implemented as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Integer | |
def times | |
1.upto(self) { yield } | |
end | |
end |
This style of coding allows for some powerful possibilities. For example, it is surprisingly easy to introduce a "do in a transaction" function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def within_a_transaction | |
start_transaction | |
yield | |
end_transaction | |
end |
Using this, I can now trivially wrap any number of statements in a transaction:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
within_a_transaction { do_something } | |
within_a_transaction do | |
do_something | |
do_something_else | |
do_a_third_thing | |
end |
The equivalent in less expressive languages, such as Java, often involves vastly more code, implementing arbitrary interfaces, anonymous inner classes, and a lot of very hard-to-read code. For comparison, here is an example of how Javas Spring Framework recommends wrapping JDBC code in transactions:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return _transactionManager.execute(new VoidDBExecCallback() { | |
public void doExecute(DBExecContext dbExecContext) throws TransactionException { | |
dbExecContext.getJdbcTemplate().execute( | |
"Select name from tbl_foo where id = ?", | |
new PreparedStatementSetter() { | |
public void setValues(PreparedStatement ps) throws SQLException | |
{ | |
ps.setInt(1, 12345); | |
} | |
}, | |
new RowCallbackHandler() { | |
public void processRow(ResultSet rs) throws SQLException | |
{ | |
String name = rs.getString(1); | |
} | |
} | |
); | |
} | |
}); |
Ruby, Day 2: Problems
The Day 2 problems are only slightly tougher than Day 1. The most fun part was coming up with a way to keep the code as concise as possible.
Print 16
Print the contents of an Array of 16 numbers, 4 numbers at a time, using just "each". Now, do the same with "each_slice" in Enumerable.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
arr = (1..16).to_a | |
arr.each { |i| print "#{i}#{i % 4 == 0 ? "\n" : ','}" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
arr = (1..16).to_a | |
arr.each_slice(4) { |slice| puts slice.join(", ") } |
Tree
Modify the Tree class initializer (original code here) so it can accept a nested structure of Hashes. Trickiest part here was that the "collect" function can call the passed in block with either one argument thats an Array or two arguments that represent the (key, value) pair.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Tree | |
attr_accessor :children, :node_name | |
def initialize(tree = {}) | |
@node_name = tree.keys[0] | |
@children = tree[@node_name].collect{ |k, v| Tree.new({k => v}) } | |
end | |
def visit_all(&block) | |
visit &block | |
children.each { |c| c.visit_all &block } | |
end | |
def visit(&block) | |
block.call self | |
end | |
end | |
tree = Tree.new({"grandpa" => {"dad" => {"child1" => {}, "child2" => {}}, "uncle" => {"child3" => {}, "child4" => {}}}}) | |
tree.visit_all { |node| puts node.node_name } |
Grep
Write a simple grep that will print the lines and line numbers of a file having any occurrence of a phrase anywhere in that line.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Usage: ruby grep.rb <regular_expression> <file_name> | |
IO.readlines(ARGV[1]).each_with_index{ |line, index| puts "#{index + 1}: #{line}" if line =~ /#{ARGV[0]}/} |
Ruby vs. Java, Round 2
I couldnt resist implementing the grep code in Java to see how it compares:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedReader; | |
import java.io.File; | |
import java.io.FileReader; | |
import java.io.IOException; | |
import java.util.regex.Pattern; | |
import java.util.regex.Matcher; | |
/** | |
* Usage: java Grep <regular_expression> <file_name> | |
*/ | |
public class Grep { | |
public static void main(String[] args) throws IOException { | |
Pattern pattern = Pattern.compile(args[0]); | |
BufferedReader br = null; | |
String line = null; | |
int lineNumber = 1; | |
try { | |
br = new BufferedReader(new FileReader(new File(args[1]))); | |
while((line = br.readLine()) != null) { | |
Matcher matcher = pattern.matcher(line); | |
if (matcher.find()) { | |
System.out.println(lineNumber + ": " + line); | |
} | |
lineNumber++; | |
} | |
} finally { | |
if (br != null) { | |
br.close(); | |
} | |
} | |
} | |
} |
Its 33 lines long. The Ruby solution was a one-liner.
Ruby, Continued
Check out more Ruby goodness on Ruby, Day 3.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.