--WkfBGePaEyrk4zXB
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

So I have a class that I may want to instantiate multiple ways.

Here's the signatures of the some so far:

  def create_from_file(absolute_filename, archive_filename, fsg=3Dnil, verb=
ose=3Dfalse, debug=3Dfalse)
  def create_from_parts(archive_filename, mtime, sha512hash)
  def create_from_string(str)

The canonical way to do this seems to be with branching in init.  You
look at the types of your arguments, and you sort of try and figure
out in initialize a clever way to distinguish between them and invoke
them correctly.

However, as you can see above, some of the methods are in a state of
flux.  Create_from_file has had several arguments appended to it, and
the first argument of each method is a string (in the second two
cases, I'm deserializing from a file, and having to convert to another
class, like Pathname, just to call a constructor is annoying).
Furthermore, I *have* changed the types and order of arguments from
time to time, this being a scripting language and all, and having to
validate the distinguisher in initialize each time is kinda lame.

Type systems are also a slightly arcane subject, deliberately avoided
in scripting languages, since not having to worry about them too much
is perceived as a major benefit.

Further, should you change the signature of the methods, you have to
go back and figure out whether that clever distinguisher in initialize
actually still works.  If you forget, which will likely happen just as
often as C/C++ programmers forget to update function prototypes in
header files, your run-time behavior will be unpredictable.  I
wouldn't want to waste time debugging that.

What I did at first was avoid initialize do nothing (actually don't
even define one) and have each of the create_from_* methods return
self at the end.  Then, I could do this:

m =3D Metadata.new().create_from_file(...)

Great, that's pretty cool, but it's a little tedious (less so if you
leave off the empty parens).

So the other way I looked into was to create some static or class
methods which created the object myself, and avoid new entirely.
After all, there's nothing too magic about new, and if it can't do
anything too fancy, emulating polymorphism in a scripting language
using branching and RTTI is totally lame.

So I looked into this a bit, and I assume something like this could
work:

  def self.create_from_file(*args)
      return self.new.create_from_file(*args)
  end

Then I realized that I've got six methods now to do initialization;
three class methods (which are a little obscure to noobs) and three
instance methods.  That's a lot of verbosity... is there a better way?

Then it dawned on me:

  def initialize(method=3Dnil, *args)
    case method
      when :file then create_from_file(*args)
      when :parts then create_from_parts(*args)
      when :string then create_from_string(*args) # unused
      # if no method, do no intialization
    end
  end

This method seems to be the best of many worlds; I get a single
initialize, I don't have to do any retarded run-time type checking to
emulate polymorphism, I can alter the signatures of the create_from_*
methods at will, and I never have to look at initialize, unless
I want to add a new kind of initializer, which is a bit rare.

Some suggest that this violates the POLS/POLA, but it seems quite
explicit to me (MUCH easier for a newbie to read than RTTI
comparisons), more terse than multiple class constructors,
substantially more robust in the face of changes to initializer type
signatures than the magic initializer technique, scales well to large
numbers of possible constructors, and is even backwards compatible
with my old method (due to method=3Dnil; you simply don't pass any
arguments and it gives you an uninitialized object; you can then call
create_from_* on it as before).

Opinions?

PS: As you see above, I'm passing verbose and debug around a lot and
hate it.  I'd like to use globals, but it seems kind of fugly to have
classes accessing globals which are only used when the __FILE__ =3D=3D
$0.  In fact, I always want verbose and debug off except when invoked
as a script.  Is there any cleaner way to deal with this?
--=20
A Weapon of Mass Construction
My emails do not have attachments; it's a digital signature that your mail
program doesn't understand. | http://www.subspacefield.org/~travis/=20
If you are a spammer, please email john / subspacefield.org to get blackliste=
d.

--WkfBGePaEyrk4zXB
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (OpenBSD)

iQIcBAEBAgAGBQJMUNEpAAoJEGQVZZEDJt9HVYkP/jKnIoR6F6DqVdvE8uQdPYWd
ir4y183hCLD0mcZSJI3LnwfWj/NS5Edz5j84kws7ljGxa8+j1W7rjy2YbjFGMIeG
nDNQeSQLIi3emUYnsvGyJTLfeVnWJxXARy9Gb7qG9I0UX0c63SYLv2EyA8OeTIwH
PM5XoNTBVMZewXEhIL7AWONqpE3+NPfZyMyvuQEJICwxWM4fipdrB2/zUkam5us4
/zsReO8zIKVg9nA2t2BpEYYojQpYqik6mFqNbARV2wpdKGHlYK+rYwr56ZJQ0TdP
LBMTcsiJVz2LYsWWVeJCAbsRTy6DppL1ClEBH0M4IxxBY8j2KJfiPVI6cCMTXcFP
LRvr3DkcYJkekFtLS+pxui3D3cSmMrJy4/sbfzENwrXx/PdjYHsuUqtZK97YmBtn
m4vQwozm/zCuLQAogFhVCJ3/uBTlx9zjn0tYwSqnDcx7vnxEKTGbwZXZwlM2+qkM
C5gNre3bu/Vt6XRLRGbPFyJOHDwQi7Kp7pLQerqGrOBgWEsHgy2lg3IaLKhWb/bn
19u4pkVG7VNW2k8n1ctXqWAw7vOCGLVj35kVlxUfV0946wZKizOPQtCrVyoEbyZB
A/K25vuXQMUWIeQUkOTMPL6Jh9sBjslncKVpRlVUS90DHli1AdrcI4EeYQgaykJ3
uxNJAA6ZMCQDtfjDhQy1
=Ecnr
-----END PGP SIGNATURE-----

--WkfBGePaEyrk4zXB--

So I have a class that I may want to instantiate multiple ways.

Here's the signatures of the some so far:

  def create_from_file(absolute_filename, archive_filename, fsg=3Dnil, verb=
ose=3Dfalse, debug=3Dfalse)
  def create_from_parts(archive_filename, mtime, sha512hash)
  def create_from_string(str)

The canonical way to do this seems to be with branching in init.  You
look at the types of your arguments, and you sort of try and figure
out in initialize a clever way to distinguish between them and invoke
them correctly.

However, as you can see above, some of the methods are in a state of
flux.  Create_from_file has had several arguments appended to it, and
the first argument of each method is a string (in the second two
cases, I'm deserializing from a file, and having to convert to another
class, like Pathname, just to call a constructor is annoying).
Furthermore, I *have* changed the types and order of arguments from
time to time, this being a scripting language and all, and having to
validate the distinguisher in initialize each time is kinda lame.

Type systems are also a slightly arcane subject, deliberately avoided
in scripting languages, since not having to worry about them too much
is perceived as a major benefit.

Further, should you change the signature of the methods, you have to
go back and figure out whether that clever distinguisher in initialize
actually still works.  If you forget, which will likely happen just as
often as C/C++ programmers forget to update function prototypes in
header files, your run-time behavior will be unpredictable.  I
wouldn't want to waste time debugging that.

What I did at first was avoid initialize do nothing (actually don't
even define one) and have each of the create_from_* methods return
self at the end.  Then, I could do this:

m =3D Metadata.new().create_from_file(...)

Great, that's pretty cool, but it's a little tedious (less so if you
leave off the empty parens).

So the other way I looked into was to create some static or class
methods which created the object myself, and avoid new entirely.
After all, there's nothing too magic about new, and if it can't do
anything too fancy, emulating polymorphism in a scripting language
using branching and RTTI is totally lame.

So I looked into this a bit, and I assume something like this could
work:

  def self.create_from_file(*args)
      return self.new.create_from_file(*args)
  end

Then I realized that I've got six methods now to do initialization;
three class methods (which are a little obscure to noobs) and three
instance methods.  That's a lot of verbosity... is there a better way?

Then it dawned on me:

  def initialize(method=3Dnil, *args)
    case method
      when :file then create_from_file(*args)
      when :parts then create_from_parts(*args)
      when :string then create_from_string(*args) # unused
      # if no method, do no intialization
    end
  end

This method seems to be the best of many worlds; I get a single
initialize, I don't have to do any retarded run-time type checking to
emulate polymorphism, I can alter the signatures of the create_from_*
methods at will, and I never have to look at initialize, unless
I want to add a new kind of initializer, which is a bit rare.

Some suggest that this violates the POLS/POLA, but it seems quite
explicit to me (MUCH easier for a newbie to read than RTTI
comparisons), more terse than multiple class constructors,
substantially more robust in the face of changes to initializer type
signatures than the magic initializer technique, scales well to large
numbers of possible constructors, and is even backwards compatible
with my old method (due to method=3Dnil; you simply don't pass any
arguments and it gives you an uninitialized object; you can then call
create_from_* on it as before).

Opinions?

PS: As you see above, I'm passing verbose and debug around a lot and
hate it.  I'd like to use globals, but it seems kind of fugly to have
classes accessing globals which are only used when the __FILE__ =3D=3D
$0.  In fact, I always want verbose and debug off except when invoked
as a script.  Is there any cleaner way to deal with this?
--=20
A Weapon of Mass Construction
My emails do not have attachments; it's a digital signature that your mail
program doesn't understand. | http://www.subspacefield.org/~travis/=20
If you are a spammer, please email john / subspacefield.org to get blackliste=
d.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (OpenBSD)

iQIcBAEBAgAGBQJMUNEpAAoJEGQVZZEDJt9HVYkP/jKnIoR6F6DqVdvE8uQdPYWd
ir4y183hCLD0mcZSJI3LnwfWj/NS5Edz5j84kws7ljGxa8+j1W7rjy2YbjFGMIeG
nDNQeSQLIi3emUYnsvGyJTLfeVnWJxXARy9Gb7qG9I0UX0c63SYLv2EyA8OeTIwH
PM5XoNTBVMZewXEhIL7AWONqpE3+NPfZyMyvuQEJICwxWM4fipdrB2/zUkam5us4
/zsReO8zIKVg9nA2t2BpEYYojQpYqik6mFqNbARV2wpdKGHlYK+rYwr56ZJQ0TdP
LBMTcsiJVz2LYsWWVeJCAbsRTy6DppL1ClEBH0M4IxxBY8j2KJfiPVI6cCMTXcFP
LRvr3DkcYJkekFtLS+pxui3D3cSmMrJy4/sbfzENwrXx/PdjYHsuUqtZK97YmBtn
m4vQwozm/zCuLQAogFhVCJ3/uBTlx9zjn0tYwSqnDcx7vnxEKTGbwZXZwlM2+qkM
C5gNre3bu/Vt6XRLRGbPFyJOHDwQi7Kp7pLQerqGrOBgWEsHgy2lg3IaLKhWb/bn
19u4pkVG7VNW2k8n1ctXqWAw7vOCGLVj35kVlxUfV0946wZKizOPQtCrVyoEbyZB
A/K25vuXQMUWIeQUkOTMPL6Jh9sBjslncKVpRlVUS90DHli1AdrcI4EeYQgaykJ3
uxNJAA6ZMCQDtfjDhQy1
=Ecnr
-----END PGP SIGNATURE-----