mhuggins / dogeify

Ruby gem for converting English to Doge.
MIT License
28 stars 5 forks source link

Is unnecessarily a class #3

Open MarkDBlackwell opened 8 years ago

MarkDBlackwell commented 8 years ago

While writing my first Ruby gem, I found (via Google) your Engineering Lunch Series tutorial on gem writing. It uses the Dogeify gem as an example, and it states:

I changed Dogeify from a module to a class. The reason for doing so is that an instance of EngTagger needs to be initialized, and I don't want this initialization to have to occur every time Dogeify processes text.

Arising from this decision, presently each new instance of Dogeify instantiates a new EngTagger.

For even more efficiency—in a highly entertaining post—@mattetti (Matt Aimonetti) and @evanphx (Evan Phoenix) in effect suggest you take this basic idea even further. You can instantiate EngTagger once only (at gem load time) and keep a reference to this EngTagger instance in a constant in Dogeify's module namespace:

module Dogeify
  Tagger = EngTagger.new
end

Of course this means Dogeify needn't be a class after all! :)

As you know, due to the multicore crisis, both Elixir, the (Ruby-related) language—as well as functional programming in general—are important (read: "the new coolness").

Perhaps it's best not to teach people to write gems as classes unnecessarily. Do you think so?

If you agree, alternatively (and more easily) you could tell your readers that converting Dogeify to a class really wasn't necessary but you did so for didactic purposes. Thus, your presentation focused on gem creation. It minimized the number of concepts new to programmers from backgrounds like Java (presumably the majority). :-)

MarkDBlackwell commented 8 years ago

There are several ways of using Dogeify. The first way is to go through ... Dogeify ... itself. The second way is to modify the String or Array classes by including either core extension.

Here's some example code with Dogeify as a module (rather than as a class. Just for demonstration purposes, it prefixes with "hello", instead of actually converting to doge-speak):

class EngTagger; def hello(s) "hello #{s}" end end
module Dogeify
  Tagger=EngTagger.new
  extend self
  def dogeify(*a)
    case
    when (!a.empty?)         then              Tagger.hello a.join ''
    when (self.is_a? String) then              Tagger.hello self.to_s
    when (self.is_a?  Array) then self.map{|e| Tagger.hello e}
    else self
    end
  end
end
class String; include Dogeify end
class Array;  include Dogeify end
Dogeify.dogeify 'a'    #=> "hello a"
"abc".dogeify          #=> "hello abc"
%w(this that).dogeify  #=> ["hello this", "hello that"]

You can even fail gracefully with:

class Object; include Dogeify end
/something/.dogeify  #=> /something/
MarkDBlackwell commented 8 years ago

Even better:

class EngTagger; def hello(s) "Hello, #{s}!" end end
module Dogeify
  Tagger=EngTagger.new
  public
  extend self
  def dogeify(*a)
    return dogeify_object self if a.empty?
    dogeify_object a
  end
  private
  def array_like?(   object) object.respond_to? :map     end
  def string_like?(  object) object.is_a?        String  end
  def dogeify_object(object)
    return object.map{|e| dogeify_object e} if   array_like? object
    return process object                   if  string_like? object
    object
  end
  def process(s) Tagger.hello s end
end
class String; include Dogeify end
class Array;  include Dogeify end
Dogeify.dogeify 'a'                    #=> "Hello, a!"
"abc".dogeify                          #=> "Hello, abc!"
%w(this that).dogeify                  #=> ["Hello, this!", "Hello, that!"]
[1, %w(this that)].dogeify             #=> [1, ["Hello, this!", "Hello, that!"]]
['one', /pattern/, 3, 'four'].dogeify  #=> ["Hello, one!", /pattern/, 3, "Hello, four!"]
/pattern/.dogeify  #=> NoMethodError: undefined method `dogeify' for /pattern/:Regexp

But:

class Object; include Dogeify end
/pattern/.dogeify                      #=> /pattern/