Best practice? libs and modules

  • Thread starter Patrick Gundlach
  • Start date
P

Patrick Gundlach

Hello,

another 'best practice' question. Example: I have a library which I
think should go into

lib/foo/bar/baz.rb

Should I use 'module ... ' wrappers to reflect the directory structure?
I.e.

module Foo
module Bar
class Baz
end
end
end

I'd like to be a good ruby-citizen. My personal preference though would
be something different:

module Baz
class SomethingElse
end
end

so the scope would be

s=Baz::SomethingElse.new


Patrick
 
E

Eero Saynatkari

Patrick said:
Hello,

another 'best practice' question. Example: I have a library which I
think should go into

lib/foo/bar/baz.rb

Should I use 'module ... ' wrappers to reflect the directory structure?
I.e.

module Foo
module Bar
class Baz
end
end
end

I'd like to be a good ruby-citizen. My personal preference though would
be something different:

module Baz
class SomethingElse
end
end

so the scope would be

s=Baz::SomethingElse.new

The latter is fine. So long as your top-level namespace is
unique (i.e. the name of your project, hopefully), the
underlying structure is inconsequential.
 
K

khaines

Hello,

another 'best practice' question. Example: I have a library which I
think should go into

lib/foo/bar/baz.rb
[snip]

I'd like to be a good ruby-citizen. My personal preference though would
be something different:

module Baz
class SomethingElse
end
end

so the scope would be

s=Baz::SomethingElse.new

How you scope your class has a subjective element. Only you can decide
what the best organization of your class hierarchy is, for your uses.

However, in the general case, yes, definitely put your class under at
least one level of scoping, such as you illustrate above, especially if
you plan to release your library for others to use. Namespace collisions
are no fun.

Just by way of example, I generally organize classes under a module
namespace that reflects the name of the project. Then, under that, I will
organize similar classes under further names.

So:

Iowa::KeyValueCoding
Iowa::Caches::LRUCache
Iowa::pools::DBConnectionPool

etc...

I have even done this with certain Ruby class extensions.

So, for example, I have added some things to String and Hash, but don't
want to either force my users to use my extensions, or interfere with my
users ability to use their own extensions. So, any place where I am going
to need one of those extensions, I work with Iowa::String or Iowa::Hash
instead of String and Hash. It's a little more typing for me, but better
for my users.


Kirk Haines
 
P

Patrick Gundlach

Hello Eero and Kirk,

thank you for your valuable comments. My "namespace" is very unlikely to
collide with anything else, and yes, I have started a little rubyforge
project, so the library will be released into public.

I appreciate your replies,

Patrick
 
M

Matt Todd

For structuring my projects, I tend to keep nested modules and classes
as internal objects. That is to say, I have ProjectName::Base but then
any other dependent classes I'll stick in
ProjectName::Support::Connection or what have you. Of course, if
someone is equally likely to use Base as well as Connection, then I'll
stick them both just in ProjectName.

Of course, this really is just preference. Either way would be fine.
(And besides, I'm (very) far from being an expert, so take what I say
with a grain of salt.)

M.T.
 
E

Eric Hodel

Hello,

another 'best practice' question. Example: I have a library which I
think should go into

lib/foo/bar/baz.rb

Should I use 'module ... ' wrappers to reflect the directory
structure?
I.e.

module Foo
module Bar
class Baz
end
end
end

If you're going this way,

class Foo::Bar::Baz

leaves out lots of indentation.
 
T

Trans

So, for example, I have added some things to String and Hash, but don't
want to either force my users to use my extensions, or interfere with my
users ability to use their own extensions. So, any place where I am going
to need one of those extensions, I work with Iowa::String or Iowa::Hash
instead of String and Hash. It's a little more typing for me, but better
for my users.

There's a downside to this though. To take advantage of those
extensions your uses have to use these special subclasses too. As long
as your not changing the behavior of a built-in you're generally okay.

T.
 
R

Ryan Williams

If you're going this way,

class Foo::Bar::Baz

leaves out lots of indentation.

That's really cool!

However, it only really works if you don't reference things in the Foo
or Bar modules. E.g.:

class Foo::Bar::Baz
end

class Foo::Bar::Qux
def fetch_me_a_baz
Foo::Bar::Baz.new() # <- need to use explicit namespace here
end
end

Versus the nested case:

module Foo
module Bar
class Baz
end

class Qux
def fetch_me_a_baz
Baz.new() # <- this line is simpler
end
end
end
end

Well, that was a bit wordier than I expected it to be. I guess it's a
tradeoff that you can make depending on how many times you need to use
Baz from inside Qux. Nice!

-RYaN
 
K

khaines

There's a downside to this though. To take advantage of those
extensions your uses have to use these special subclasses too. As long
as your not changing the behavior of a built-in you're generally okay.

No, it really doesn't matter if I am changing the behavior of a built in
or not (which I'm not).

If I just opened String and put my extensions into there, I'd run the
substantial risk that someone might use a core extension from
Rails' ActiveSupport or Facets that conflicts. And even if there are no
conflicts today, tomorrow there might be.

Or if I just used LRUCache instead of Iowa::Caches::LRUCache, and someone
decided to use the Facets LRUCache in an app, it would cause bad things to
happen (I like the Facets LRUCache's simplicity; I hate that it requires
extending anything that is to be stored in it, BTW).

So yes, if one of my users wants to take advantage of something I have
in one of the extensions, they have to type a few more characters to
create the object. I am quite willing to sacrafice a few characters for
the relative security against namespace collisions, though. The cost is
very small, and the benefit is large.


Kirk Haines
 
T

Trans

No, it really doesn't matter if I am changing the behavior of a built in
or not (which I'm not).

If I just opened String and put my extensions into there, I'd run the
substantial risk that someone might use a core extension from
Rails' ActiveSupport or Facets that conflicts. And even if there are no
conflicts today, tomorrow there might be.

Or if I just used LRUCache instead of Iowa::Caches::LRUCache, and someone
decided to use the Facets LRUCache in an app, it would cause bad things to
happen (I like the Facets LRUCache's simplicity; I hate that it requires
extending anything that is to be stored in it, BTW).

Hmm... If you have a fix for that I'd love to incorporate it.
So yes, if one of my users wants to take advantage of something I have
in one of the extensions, they have to type a few more characters to
create the object. I am quite willing to sacrafice a few characters for
the relative security against namespace collisions, though. The cost is
very small, and the benefit is large.

But then how will I use Kirk's String extensions and Tom's string
extensions?

It's a tade off and what we really need is proper namespaces to fix the
problem.

T.
 
K

khaines

Hmm... If you have a fix for that I'd love to incorporate it.

Well, let me tell you what I did.

The mixin is used to turn the desired class into something that can be a
linked list node. It's a neat idea, but I don't like having to pollute my
namespace, and it breaks down messily in some instances (try using the
cache to cache fixnums...).

So, I took a couple ideas from the implementation and built a doubly
linked list implementation that doesn't need the mixin and also provides
fast random access by using a hash in conjunction with the list. So data
access, modification, inserts, and deletes are all pretty fast, and no
penalty is paid for having large numbers of items in the cache.

I then used this as the basis for an LRU cache implementation. Mine will
also expire elements based on age. So, for example, a cache with a max
ttl of 1800 seconds will expire elements that haven't been accessed in
more than half an hour, even if the cache isn't full. I pay a small cost
for this capability, though, in terms of speed, so it may not be something
you would want in your implementation.

The cache should be threadsafe, and I also have a capability in mine for
finalizers -- blocks of code that are invoked immediately before something
is expired from the cache -- which also does not come for free.

I can happily make a version of the cache which doesn't include the TTL
stuff or the finalizers, though, if you want it. Note that the mixin
approach of your current LRU cache definitely makes for the fastest cache,
though, so I guess it depends on what one is selecting for. I'm using a
modified version of your LRUCache (I added finalizer support) in a place
where I need a basic LRU cache, can live with the mixin, and just want it
to be as fast as possible.

You might also be able to use the raw linked list?
But then how will I use Kirk's String extensions and Tom's string
extensions?

I suppose I could make Iowa::String delegate any method it doesn't know
down to the original string it was initialized with. That requires that
it keep a reference to that original string, though, which is also a
tradeoff.


Kirk Haines
 
T

Trans

Well, let me tell you what I did.

The mixin is used to turn the desired class into something that can be a
linked list node. It's a neat idea, but I don't like having to pollute my
namespace, and it breaks down messily in some instances (try using the
cache to cache fixnums...).

So, I took a couple ideas from the implementation and built a doubly
linked list implementation that doesn't need the mixin and also provides
fast random access by using a hash in conjunction with the list. So data
access, modification, inserts, and deletes are all pretty fast, and no
penalty is paid for having large numbers of items in the cache.

I then used this as the basis for an LRU cache implementation. Mine will
also expire elements based on age. So, for example, a cache with a max
ttl of 1800 seconds will expire elements that haven't been accessed in
more than half an hour, even if the cache isn't full. I pay a small cost
for this capability, though, in terms of speed, so it may not be something
you would want in your implementation.

The cache should be threadsafe, and I also have a capability in mine for
finalizers -- blocks of code that are invoked immediately before something
is expired from the cache -- which also does not come for free.

I can happily make a version of the cache which doesn't include the TTL
stuff or the finalizers, though, if you want it. Note that the mixin
approach of your current LRU cache definitely makes for the fastest cache,
though, so I guess it depends on what one is selecting for. I'm using a
modified version of your LRUCache (I added finalizer support) in a place
where I need a basic LRU cache, can live with the mixin, and just want it
to be as fast as possible.

Well, I think those are useful features. It would be nice if it could
be implemented in such a way that the TTL and finializers were options
--but zero overhead when not used. But even if that's not reasonable,
then a second type of LRUCache per your implementation is still welcome
in Facets.
You might also be able to use the raw linked list?

Yes!

T.
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top