Daniel Martin
8/18/2007 2:58:00 PM
In the past week, I've been exploring a little problem that by
Wednesday I was ready to sit down and code. Now, the details of the
problem aren't important, but for this problem I was going to need to
have objects that I could read from a file, manipulate a few hundred
times, and then write back to a file.
Now, object serialization is not really what I'd consider "fun" stuff,
so I figured "I'll just throw YAML at it". Unfortunately, my objects
contained several NArray (and NMatrix and NVector) objects, and YAML
initially serialized them as:
irb(main):002:0> NArray.float(3,3).to_yaml
=> "--- !ruby/object:NArray {}\n\n"
Not helpful. Searching the web pulled up a ruby-talk message from a
few years ago, but apparently the YAML api for defining your own YAML
serialization has changed since then. Although this new API appears
to be undocumented (at least, I couldn't find any documentation), I
did find a few blog posts in which people showed examples with other
classes.
So anyway, I finally integrated YAML and NArray, and present the
solution here for other people googling in the future on how to get
these two to work together, as well as now for comment and criticism
by current list subscribers. Note that it handles NArray, NMatrix,
and NVector objects:
########## narray-yaml.rb #########
require 'yaml'
require 'narray'
# First, a yaml utility routine to make the output prettier
module YAML
class YIArray < Array
yaml_as "tag:yaml.org,2002:seq"
def to_yaml_style; :inline; end
end
def YAML.inline_array(arr)
if Array === arr[0] then
arr.map {|s| inline_array(s)}
else
YIArray.new.concat(arr)
end
end
end
class NArray
# I used a tag of my own since I wasn't sure if
# someone else had their own serialization of
# NArray objects
yaml_as "tag:martin@snowplow.org,2007:narray"
def to_yaml( opts = {} )
YAML::quick_emit( self.object_id, opts ) do |out|
out.map(taguri) do |map|
map.add('typecode', typecode)
map.add('shape', YAML::inline_array(shape))
map.add('data', YAML::inline_array(to_a))
end
end
end
def self.yaml_new(klass, tag, val)
result = klass.new(val['typecode'], *(val['shape']))
result[] = val['data']
result
end
end
__END__
The result looks like this, for a 2 by 2 by 2 NArray:
--- !martin@snowplow.org,2007/narray
typecode: 5
shape: [2, 2, 2]
data: !seq:Array
- !seq:Array
- [0.441292242643088, 0.463176002629107]
- [0.602508503899103, 0.155671031769272]
- !seq:Array
- [0.558707757356912, 0.536823997370893]
- [0.397491496100897, 0.844328968230728]
So I'm pretty happy with the output format, but not happy with how
long it took to get there, both in terms of the amount of code and in
terms of the amount of time spent tracking stuff down, and the amount
of time spent looking at documentation for the wrong API trying to
figure out why my code wasn't working.
Any suggestions on how to clean up my code, and any pointers to
documentation I missed?
--
s=%q( Daniel Martin -- martin@snowplow.org
puts "s=%q(#{s})",s.to_a.last )
puts "s=%q(#{s})",s.to_a.last