Module philosophy

L

Leslie Viljoen

Sorry to beat a dead horse, but to confirm: the only way to mix a
module into an already
existing class it to reopen it with something (ugly) like this:

class String; include Crc; end

(1) Is that right?

And then to mix a module into an instantiated object, you can use
extend, and that
modifies the one object. (2) Philosophically, is there any reason why
extend can't be used
in the same way on classes? (ie. String.extend(Crc))


One other question (3), below is an example of my SelfCrc module. This module
works well when included into String, but extend doesn't work because that
only modifies the one object and .crc16_ok? below tries to call .get_crc16
on a new string that it creates, which fails.

I can modify it to work like so:

def crc16_ok?
first_part = self[0..-3]
first_part.extend(SelfCrc)
first_part.get_crc16 == get_terminating_crc16
end

...is that the right way to make this module work properly? Or are
some modules just not suitable for extend?


module SelfCrc

# Returns the CCITT CRC16 for the current object
#
def get_crc16
@@crctab ||= CrcTab.new
@@crctab.get(self)
end

# Converts the last two characters of this string to
# a numeric CRC and returns it
#
def get_terminating_crc16
(self[-2, 1][0] << 8) + (self[-1, 1][0])
end

# Calculates a CRC for the string, converts it into two
# characters and adds them to the end of the string
#
def add_terminating_crc16!
replace(self + get_crc16.to_s16)
end

# Removes the last two characters from the string
#
def remove_terminating_crc16!
replace(self[0..-3])
end

# Extracts two characters from the end of the string, converts
# them to a numeric CRC and compares that CRC to the CRC of
# the rest of the string. Returns true if the CRC is correct.
#
def crc16_ok?
self[0..-3].get_crc16 == get_terminating_crc16
end
end



Thanks!

Leslie
 
S

Stefano Crocco

modifies the one object. (2) Philosophically, is there any reason why
extend can't be used
in the same way on classes? (ie. String.extend(Crc))

Because String.extend(Crc) would add the instance methods of the Crc module as
class methods of String, not as instance methods. This would mean you could
do:

String.method_defined_in_crc

but not

"hello".method_defined_in_crc

If you don't like the idea of reopening the class, you can use include with
send:

String.send :include, Crc

Stefano
 
R

Robert Klemme

Sorry to beat a dead horse, but to confirm: the only way to mix a
module into an already
existing class it to reopen it with something (ugly) like this:

class String; include Crc; end

(1) Is that right?

And then to mix a module into an instantiated object, you can use
extend, and that
modifies the one object. (2) Philosophically, is there any reason why
extend can't be used
in the same way on classes? (ie. String.extend(Crc))

You need to be able to distinguish between importing class methods and
importing instance methods (see Stefano's reply).
One other question (3), below is an example of my SelfCrc module. This module
works well when included into String, but extend doesn't work because that
only modifies the one object and .crc16_ok? below tries to call .get_crc16
on a new string that it creates, which fails.

I can modify it to work like so:

def crc16_ok?
first_part = self[0..-3]
first_part.extend(SelfCrc)
first_part.get_crc16 == get_terminating_crc16
end

..is that the right way to make this module work properly? Or are
some modules just not suitable for extend?

If your module expects to be present in all instances of the class
then including is the only reasonable way to do it IMHO.

I can think of a number of other designs which would not have this
issue but since I do not exactly know what your module does
(especially CrcTab which could be a kind of cache) I am reluctant to
enumerate them.

Assuming that you want to store strings along with their CRC's I'd
rather have a separate class that has a String and a CRC. That seems
more natural to me. This would also avoid the issue that you cannot
easily determine whether a String does contain its CRC or not etc.

Kind regards

robert
 
D

David Masover

Sorry to beat a dead horse, but to confirm: the only way to mix a
module into an already
existing class it to reopen it with something (ugly) like this:

class String; include Crc; end

(1) Is that right?

Short answer: You'll have to mess with it dynamically, but that is far from
the only syntax to do so. Other obvious ones:

String.class_eval { include Crc }
String.send :include, Crc

And there are always stranger things like:

"foo".class.class_eval { include Crc }
And then to mix a module into an instantiated object, you can use
extend, and that
modifies the one object.

Yes, but keep in mind: "include"ing a module into a class will affect all
existing and future instances of that class, and of any subclasses. So you're
probably not going to extend if you include.
(2) Philosophically, is there any reason why
extend can't be used
in the same way on classes? (ie. String.extend(Crc))

As others have said, they do different things -- include adds instance methods
to a class, and extend adds methods to an object.

It's a tricky concept, but remember that classes are objects, too. So:

some_string.extend(Crc) # will define some_string.crc16_ok?
String.extend(Crc) # will define String.crc16_ok?

So, extend works on the actual object you use it on, and include works on any
instances of it. You could do this, for example:

Class.send :include, Crc
# Now all classes will have a crc16_ok? class method, for example:
String.crc16_ok?

That's probably not what you want.
...is that the right way to make this module work properly? Or are
some modules just not suitable for extend?

I think that comes down to personal philosophy -- I haven't seen (or
used) 'extend' a whole lot. It seems most useful when you'd only want to
apply it to a single object, and then, I'd usually use instance_eval instead.

On the other hand, including it into String means you're affecting all
Strings, and polluting String's method namespace. It means that no one else
can define String#get_crc16.

The other way would be to use a separate class -- either something which
contains both a String and a CRC (as Robert suggested), or something which
inherits from String.

And either way, depending on how much work you want to do, you could always
override all methods on String which return a String, and force them to
either return an extended String, or return a CrcString, depending on how you
decide to do it.
 
L

Leslie Viljoen

Holy macaroni, thanks for these replies!

What I am trying to accomplish here is the optional modification of
core classes. So my CRC lib can handle useful calculations, and
optionally add convenient methods to String and Fixnum too.

That way people can have the convenience if they like, but they must
explicitly authorise it with String.send:)include ..). It seems like a
good plan to me, but the lack of a public String.include() method
makes me think this is a very rare plan.

These strings with CRC's on the ends are coming in from sockets, and I
have to check them, extract them, use them and then put CRC's on the
end of the replies before sending them out. In this case it's quite
nice to do:

begin
msg, sender = @socket.recvfrom(1500)
end while (!msg.crc16_ok?)
msg.remove_terminating_crc16!
...

- but it's true that I might forget to remove that crc. So perhaps
decend from String like you say:

begin
msg, sender = @socket.recvfrom(1500)
msg = CrcString(msg)
end while (!msg.crc16_ok?)
(now use msg directly)

- and I'd know immediately if I'd forgotten to construct the CrcString
because crc16_ok? would fail.


And then a third option would be to modify Socket:
class Socket
def recv_checked(n)
CrcString(recvfrom(n))
end
end

..which I'd again attempt by providing a module that I'd
Socket.send:)include, CrcSocket)

Or once again I could override both String and Socket to make
CrcString and CrcSocket which returns CrcStrings. You're never short
on options with Ruby!

Les
 
M

Marc Heiler

It seems like a good plan to me, but the lack of a
public String.include() method makes me think this is a
very rare plan.

It might be rare because in reality this is not so much a
problem IMHO, despite some people claiming it could be. (I had
an angry IRC "discussion" with a C# proponent over this, I
really dislike people iterating about the "danger" of concepts
despite others applying them for many years without any
problem ... )

What could be nice is to store different "states" of ruby classes
and restore to them at user's will. (Or additionally restore the
"start" the state of slowly "evolved" ruby classes as well).

I mean both standard classes (more important) and user-built
ones (less important, IMHO). It is interesting to see that
someone like the sandbox of _why was more an afterthought
than an initial design idea. I think there is plenty of
more room in this direction, who knows... maybe in a few
years. Ruby is like the OOP language that goes towards
new paradigms - with class derived objects - the most easiest.

Ruby is great but it is not perfect for everyone or every
use case. And since 99% of new languages will have crappy
syntax, the most likely language to beat Ruby will be ...

Ruby! :)
 
D

David Masover

Holy macaroni, thanks for these replies!

What I am trying to accomplish here is the optional modification of
core classes. So my CRC lib can handle useful calculations, and
optionally add convenient methods to String and Fixnum too.

Probably included, then, rather than extended.
That way people can have the convenience if they like, but they must
explicitly authorise it with String.send:)include ..). It seems like a
good plan to me, but the lack of a public String.include() method
makes me think this is a very rare plan.

Generally, people don't expect to have to do more than a require to have the
library ready to use. So I'd do that yourself, somewhere in the library file.
 
L

Leslie Viljoen

Perhaps people don't have this problem very often, but I once spent
three days puzzling over a Rails app that worked fine on my computer,
but not at the web host. A library had been installed by the host that
created a class that was require'd somewhere down the line. That class
had the same name as one of my models, and it totally killed my app.

Having a dynamic base environment works fine until there's a clash. As
the code base grows, so do the chances of a clash and also the
difficulty in finding it.
 
D

Donald Ball

Perhaps people don't have this problem very often, but I once spent
three days puzzling over a Rails app that worked fine on my computer,
but not at the web host. A library had been installed by the host that
created a class that was require'd somewhere down the line. That class
had the same name as one of my models, and it totally killed my app.

Having a dynamic base environment works fine until there's a clash. As
the code base grows, so do the chances of a clash and also the
difficulty in finding it.

I had a similar experience a while back wherein I inadvertently
overrode a method defined by ActiveRecord somewhere. At the time, I
wondered if it might be a good idea to have the ruby interpreter warn
when a method was overridden, unless either the original method was
flagged as silently over-ridable (e..g Object.to_s) or the overriding
method was flagged as intentional (e.g. overriding
ActiveRecord::Base.find). It's almost certainly not worth the hassle
even if the approach is sound, but after spending some hours digging
through the darker corners of the ActiveRecord code, it seemed like an
awfully attractive notion.

- donald
 
L

Leslie Viljoen

ruby -w does warn on method redefinitions!

I had a similar experience a while back wherein I inadvertently
overrode a method defined by ActiveRecord somewhere. At the time, I
wondered if it might be a good idea to have the ruby interpreter warn
when a method was overridden, unless either the original method was
flagged as silently over-ridable (e..g Object.to_s) or the overriding
method was flagged as intentional (e.g. overriding
ActiveRecord::Base.find). It's almost certainly not worth the hassle
even if the approach is sound, but after spending some hours digging
through the darker corners of the ActiveRecord code, it seemed like an
awfully attractive notion.

- donald
 
D

David Masover

Perhaps people don't have this problem very often, but I once spent
three days puzzling over a Rails app that worked fine on my computer,
but not at the web host. A library had been installed by the host that
created a class that was require'd somewhere down the line. That class
had the same name as one of my models, and it totally killed my app.

How was it require'd, though?

That's the thing -- yes, there will be a clash, but it'll very likely be found
as soon as you require both of them.
 
D

Donald Ball

ruby -w does warn on method redefinitions!

True, and I ought to have thought to try that at the time, but unless
I missed something, there's no way to flag if your overrides are
intentional or not.

- donald
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,737
Latest member
Georgeengab

Latest Threads

Top