From: Henrik Martensson Date: 2006-01-07T19:33:21+09:00 Subject: Re: Iterator Fu Failing Me On Sat, 2006-01-07 at 05:36, James Edward Gray II wrote: > I have a group of classes, all implementing a parse?() class method. > Calling parse?(token) will return the constructed object, if it can > be parsed by this class, or false otherwise. > > I want to run a bunch of tokens through these classes, grabbing the > first fit. For example: > > elements = tokens.map do |token| > ClassA.parse?(token) or > ClassB.parse?(token) or > ClassC.parse?(token) > end > > That works. Now can anyone give me a version of the middle section > that doesn't require I call parse?() 50 times? I want something I am not certain if this solution fits, but... There is a refactoring named Replace Conditional with Polymorphism that might solve your problem. The idea is that when you have a conditional, you can create a class hierarchy with one subclass for each leg in the conditional. (See http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html for a slightly better explanation.) You already have several different parser classes, and of course you don't need to bother with a class hierarchy, so you could do something like: parser = ParserFactory.create(parser_type) ... elements = tokens.map do |token| parser.parse?(token) end Note that 'parser_type' may well be the class of a token in tokens. This works assuming that all tokens in 'tokens' are of the same type. For example, you have one parser for handling CSV data, another for XML, a third for SGML, a fourth for non-compliant HTML, etc. On the other hand, if each parser class handles a subset of the tokens you get from one type of input, for example you have tokenized an XML file, and have one parser for elements, another for processing instructions, a third for text nodes, etc., you will need something different. A case statement would do the trick: elements = tokens.map do |token| case token when Element ElementParser.parse?(token) when Text TextParser.parse?(token) ... else raise "Unknown token type" end end You can then factor out the case statement into a method named parse?, move the parse? method to a class of it's own, and be back to: elements = tokens.map do |token| ParseEverything.parse?(token) end A while ago I wrote a CSV parser in Java for a talk on Test Driven Design. The Java code got horribly complex, but it struck me that in Ruby I could do this: class Parser def parse(reader, writer) tokenize(reader) { |type| |value| write(type, value, writer) } end end By mixing in tokenize and write methods, it would be possible to build parsers that handle most formats. I hope this helps. /Henrik -- http://www.henrikmartensson.org/ - Reflections on software development