While working on a problem this morning I came up with an interesting technique for using the BitStruct gem to produce binary strings. After some hacking around I discovered I didn't need to use BitStruct at all. Even so I thought I would share what I learned so the technique doesn't get lost.
Imagine you need to construct a binary string from some arbitrary object. BitStruct allows you to do this in 3 ways: pass a binary string directly, pass in a hash, or yield a block. It's easy to extend this mechanism for parsing your own classes by building on the block yield initialization mechanism.
hsh = {:a => 1, :b => "foo", :c => "bar"}
class C < BitStruct
signed :context, 32, "c id"
char :password, 12*8, "pw"
char :usr_name, 12*8, "name"
# important to initialize these values so the
# block passed to #super is evaluated correctly
initial_value.context = 0
initial_value.password = ''
initial_value.usr_name = ''
def initialize message
# parens on #super are important so no args
# are passed up to the parent; if this part fails
# then you missed
super() do |struct|
struct.context = message[:a]
struct.password = message[:b]
struct.usr_name = message[:c]
end
end
end
c = C.new hsh # very clean!
c.inspect
In this example I passed in a hash as my message, but the +message+ variable could have been any object that could be interrogated to retrieve values for setting the bitstruct fields. I like this technique because it delegates the responsibility of proper bitstruct initialization to the class under construction. It nicely encapsulates that operation which I believe demonstrates the Single Responsibility principle.
I hope this is of use to someone someday.
cr
Chuck Remes wrote:
def initialize message
# parens on #super are important so no args
# are passed up to the parent; if this part fails
# then you missed
super() do |struct|
struct.context = message[:a]
struct.password = message[:b]
struct.usr_name = message[:c]
If you add this line here:
yield struct if block_given?
then the block initialization can still be used by subclasses or by the caller of #new:
c = C.new hsh do |struct|
struct.context = 2
end
···
--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
Chuck Remes wrote:
While working on a problem this morning I came up with an interesting technique for using the BitStruct gem to produce binary strings. After some hacking around I discovered I didn't need to use BitStruct at all. Even so I thought I would share what I learned so the technique doesn't get lost.
Imagine you need to construct a binary string from some arbitrary object. BitStruct allows you to do this in 3 ways: pass a binary string directly, pass in a hash, or yield a block. It's easy to extend this mechanism for parsing your own classes by building on the block yield initialization mechanism.
hsh = {:a => 1, :b => "foo", :c => "bar"}
class C < BitStruct
signed :context, 32, "c id"
char :password, 12*8, "pw"
char :usr_name, 12*8, "name"
# important to initialize these values so the
# block passed to #super is evaluated correctly
initial_value.context = 0
initial_value.password = ''
initial_value.usr_name = ''
def initialize message
# parens on #super are important so no args
# are passed up to the parent; if this part fails
# then you missed
super() do |struct|
struct.context = message[:a]
struct.password = message[:b]
struct.usr_name = message[:c]
end
end
end
c = C.new hsh # very clean!
c.inspect
In this example I passed in a hash as my message, but the +message+ variable could have been any object that could be interrogated to retrieve values for setting the bitstruct fields. I like this technique because it delegates the responsibility of proper bitstruct initialization to the class under construction. It nicely encapsulates that operation which I believe demonstrates the Single Responsibility principle.
I hope this is of use to someone someday.
cr
I think I see where you're going with that, but just so others know, the hash-based initialization is simple (though it does require that the hash keys match the field names--and avoiding this is probably the point of your code):
require 'bit-struct'
hsh = { :context => 1, :password => "foo", :usr_name => "bar" }
class C < BitStruct
signed :context, 32, "c id"
char :password, 12*8, "pw"
char :usr_name, 12*8, "name"
end
c = C.new hsh
···
--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
Joel,
that's right. I probably shouldn't have used a hash as my example since you already have hash-based initialization built in. I meant for this to be a nice way to pass an arbitrary object to the constructor so the logic of getting data from that object would be encapsulated in one spot. I also could have built a temporary hash from the object argument and passed that to the superclass' constructor but I prefer the block-based initialization for readability.
And thanks for creating such a neat library. I don't have a need for it now that I understand my problem domain better, but rest assured it is now a member of my toolbox for some future problem when I do need it.
cr
···
On May 14, 2009, at 4:53 PM, Joel VanderWerf wrote:
Chuck Remes wrote:
In this example I passed in a hash as my message, but the +message+ variable could have been any object that could be interrogated to retrieve values for setting the bitstruct fields. I like this technique because it delegates the responsibility of proper bitstruct initialization to the class under construction. It nicely encapsulates that operation which I believe demonstrates the Single Responsibility principle.
I hope this is of use to someone someday.
cr
I think I see where you're going with that, but just so others know, the hash-based initialization is simple (though it does require that the hash keys match the field names--and avoiding this is probably the point of your code):
require 'bit-struct'
hsh = { :context => 1, :password => "foo", :usr_name => "bar" }
class C < BitStruct
signed :context, 32, "c id"
char :password, 12*8, "pw"
char :usr_name, 12*8, "name"
end
c = C.new hsh