Paul R. Potts
Ruby is definitely one of my favorite languages, and I am far more comfortable with it than I am with Perl. It seems very expressive; very frequently, once I find the right library method, the code pretty much writes itself.
However, just like with Perl, there is more than one way to do it, so I often find myself looking for more appropriate idioms for the task at hand. There is also some question of efficiency; I’m not trying to optimize prematurely, but the program I’m working on runs slower than I’d like, and I think Ruby can do better.
To start, I’ll just share one quick annoyance. The first is that there is some deficiency in handling of typing such that statements like
puts "Checksum: " + chksm
will not work; you get a run-time error (can’t convert Fixnum into String).” This seems wrong; it is not very “DWIM” (do what I mean) when considered in light of Ruby’s philosophy of weak “duck typing” – if it quacks, for all practical purposes your program can treat it as a duck, or in this case a string. I bring this up because I keep getting this error – I have a habit of forgetting to add .to_s to the variable. I’m thinking in terms of C++ iostreams, where the type is taken care of using the stream << operator.
But on to a meatier question. I want to be able to treat a string containing ASCII hex digits as an array of bytes or as a Fixnum (where I can specify the byte ordering). For example, I want to be able to turn
“DEADBEEF” into an array of unsigned integers [0xDE, 0xAD, 0xBE, 0xEF] (for purposes of generating a bytwise checksum), or into the unsigned integers 0xDEAD, 0xDEADBE, or 0xDEADBEEF (depending on the record type I’m working on).
These are the ASCII character values (exprssed in hex); ‘D’ is decimal 68, hex 44. Not very useful; I get 2 strings of ASCII hex out. These aren’t very useful for translation, since I’d have to turn them into integers, translate them to numeric values (not just a simple offset, because the hex characters are not contiguous) and then assemble the high and low nybbles into byte values using (high << 4) + low, or some such.
There must be a better way to do this. Here’s my first try:
def make_checksum (str)
checksum = 0
0.step(str.length - 1, 2) { |idx|
checksum += (str[idx].chr.hex * 16) + (str[idx + 1].chr.hex)
}
return (~checksum) & 0xFF
end
Ugh. Look at the way I have to access the characters: str[idx] returns numeric types, not character types, which don’t have a hex() method, so I have to convert them from the original characters in the string to integers, to characters, and then apply hex() to that.
Another way is to make substrings, I suppose, but it doesn’t perform better, probably because it is generating a lot of extra string objects:
def make_srec_checksum (str)
puts "make_srec_checksum; str is: " + str
checksum = 0
0.step(str.length - 1, 2) { |idx|
checksum += str[idx..(idx + 1)].hex
}
return (~checksum) & 0xFF
end
Another is that the String.unpack() method seemed from reading the documentation that it had exactly what I needed; it appeared that the “h” and “H” characters in the control string for the unpack method would be capable of unpacking ASCII hex data like DEADBEEF into byte values. That seems to be what unpack is all about. Instead, it generates ASCII hex bytes.
$ ruby -e 'puts "0123456789ABCDEF".unpack("H2" * 16)'
30
31
32
33
34
35
36
37
38
39
41
42
43
44
45
46
Not what I wanted. Most likely, there is a much better way!
It might be interesting to compare Common Lisp and Ruby implementations of my SREC tool; how concise, yet expressive, can I be in each language? This also might serve to shed some light on whether Common Lisp has fallen behind as far as libraries for typical file-handling and scripting-type tasks. More on that later.