This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
= Cloneable
|
||||
|
||||
Require the library.
|
||||
|
||||
require 'facets/cloneable'
|
||||
|
||||
We'll use this dummy class.
|
||||
|
||||
class Foo
|
||||
include Cloneable
|
||||
def initialize
|
||||
@bar=[]
|
||||
end
|
||||
def bar_id
|
||||
@bar.object_id
|
||||
end
|
||||
end
|
||||
|
||||
Try #dup.
|
||||
|
||||
a = Foo.new
|
||||
b = a.dup
|
||||
b.bar_id.refute == a.bar_id
|
||||
|
||||
a.taint
|
||||
b = a.dup
|
||||
b.assert.tainted?
|
||||
|
||||
a.freeze
|
||||
b = a.dup
|
||||
b.refute.frozen?
|
||||
|
||||
Note try #clone.
|
||||
|
||||
a = Foo.new
|
||||
b = a.clone
|
||||
b.bar_id.refute == a.bar_id
|
||||
|
||||
a.taint
|
||||
b = a.dup
|
||||
b.assert.tainted?
|
||||
|
||||
a.freeze
|
||||
b = a.clone
|
||||
b.assert.frozen?
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
= Enumerable::Arguments
|
||||
|
||||
Require the library.
|
||||
|
||||
require 'facets/enumargs'
|
||||
|
||||
This will serve as our example class.
|
||||
|
||||
class PlusArray
|
||||
include Enumerable::Arguments
|
||||
|
||||
def initialize(arr)
|
||||
@arr = arr
|
||||
end
|
||||
|
||||
def each(n=0)
|
||||
@arr.each{ |e| yield(e+n) }
|
||||
end
|
||||
end
|
||||
|
||||
Now Enumerable methods such as #map and #collect
|
||||
take an argument as well.
|
||||
|
||||
t = PlusArray.new([1,2,3])
|
||||
t.collect(4){ |e| e }.assert == [5,6,7]
|
||||
|
||||
Filtering methods such as #select and reject work
|
||||
as well.
|
||||
|
||||
t = PlusArray.new([1,2,3])
|
||||
t.select(4){ |x| x == 6 }.assert == [6]
|
||||
|
||||
t = PlusArray.new([1,2,3])
|
||||
t.reject(4){ |x| x == 6 }.assert == [5,7]
|
||||
|
||||
We can covert to an array using #to_a with an argument.
|
||||
|
||||
t = PlusArray.new([1,2,3])
|
||||
t.to_a(4).assert == [5,6,7]
|
||||
|
||||
We can get the minimum value using #min.
|
||||
|
||||
t = PlusArray.new([1,2,3])
|
||||
t.min(4).assert == 5
|
||||
|
||||
And get the maximum value using #min.
|
||||
|
||||
t = PlusArray.new([1,2,3])
|
||||
t.max(4).assert == 7
|
||||
|
||||
Methods that already take and argument, now take two
|
||||
such as #include?
|
||||
|
||||
t = PlusArray.new([1,2,3])
|
||||
t.assert.include?(7,4)
|
||||
|
||||
And #each_slice.
|
||||
|
||||
t = PlusArray.new([1,2,3,4])
|
||||
a = []
|
||||
t.each_slice(2,4){ |e,f| a << [e,f] }
|
||||
a.assert == [[5,6],[7,8]]
|
||||
|
||||
The #find method has a slightly different interface
|
||||
than the original Enumerable.
|
||||
|
||||
t = PlusArray.new([1,2,3,4])
|
||||
f = t.find(2, :ifnone=>lambda{:NOPE}) { |a| a == 10 }
|
||||
f.assert == :NOPE
|
||||
|
||||
The #grep method should also work.
|
||||
|
||||
t = PlusArray.new(['a1','b2','a3','b4'])
|
||||
t.grep(/^b/, 'x').assert == ['b2x','b4x']
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
= Equitqable
|
||||
|
||||
Require the library.
|
||||
|
||||
require 'facets/equitable'
|
||||
|
||||
We will use this simple class as an example.
|
||||
|
||||
class C
|
||||
include Equitable(:a,:b)
|
||||
|
||||
attr_accessor :a, :b
|
||||
|
||||
def initialize(a,b)
|
||||
@a = a
|
||||
@b = b
|
||||
end
|
||||
end
|
||||
|
||||
Now, if two instance of our sample class +C+ have
|
||||
equal attributes +@a+ and +@b+ then they will
|
||||
be equal.
|
||||
|
||||
c1 = C.new(10,20)
|
||||
c2 = C.new(10,20)
|
||||
c2.assert == c1
|
||||
|
||||
Otherwise they will not be equal.
|
||||
|
||||
c1 = C.new(10, 10)
|
||||
c2 = C.new(10, 20)
|
||||
c2.refute == c1
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
= Instantiable
|
||||
|
||||
Require the library.
|
||||
|
||||
require 'facets/instantiable'
|
||||
|
||||
We will use this module as an example.
|
||||
|
||||
module M
|
||||
include Instantiable
|
||||
|
||||
attr :a
|
||||
|
||||
def initialize(a)
|
||||
@a = a
|
||||
end
|
||||
end
|
||||
|
||||
As we can see M is now fully instantiable, as any
|
||||
class would be.
|
||||
|
||||
m = M.new(1)
|
||||
m.a.assert == 1
|
||||
|
||||
We can also use #extend rather than #include and
|
||||
get the same result.
|
||||
|
||||
module N
|
||||
extend Instantiable
|
||||
|
||||
attr :a
|
||||
|
||||
def initialize( a )
|
||||
@a = a
|
||||
end
|
||||
end
|
||||
|
||||
As we saw with +M+ so too with +N+.
|
||||
|
||||
n = N.new( 1 )
|
||||
n.a.assert == 1
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
== BasicObject
|
||||
|
||||
require 'facets/basicobject'
|
||||
|
||||
o = Class.new(BasicObject).new
|
||||
|
||||
Should ignore new kernel methods.
|
||||
|
||||
Kernel.module_eval { def xxx ; end }
|
||||
|
||||
expect NoMethodError do
|
||||
o.xxx
|
||||
end
|
||||
|
||||
Should ignore new object methods.
|
||||
|
||||
Object.class_eval { def yyy ; end }
|
||||
|
||||
expect NoMethodError do
|
||||
o.yyy
|
||||
end
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
= Memoizable
|
||||
|
||||
Memoizable allows
|
||||
|
||||
require 'facets/memoizable'
|
||||
|
||||
The classic example is calculating the Fibonacci sequence.
|
||||
|
||||
class Fibonacci
|
||||
include Memoizable
|
||||
|
||||
def fib( num )
|
||||
return num if num < 2
|
||||
fib(num - 1) + fib(num - 2)
|
||||
end
|
||||
|
||||
memoize :fib
|
||||
end
|
||||
|
||||
The first time will take a moment to calculate.
|
||||
|
||||
f = Fibonacci.new
|
||||
f.fib(100)
|
||||
|
||||
The second time will be returned almost instantly.
|
||||
|
||||
f = Fibonacci.new
|
||||
f.fib(100)
|
||||
|
||||
The following example makes if very clear what memoize is doing.
|
||||
|
||||
class MemoExample
|
||||
include Memoizable
|
||||
attr_accessor :a
|
||||
memoize :a
|
||||
end
|
||||
|
||||
m = MemoExample.new
|
||||
m.a = 10
|
||||
m.a #=> 10
|
||||
m.a = 20
|
||||
m.a #=> 10
|
||||
|
||||
Even though @a is assigned to 20, the method #a continues to return 10.
|
||||
|
||||
Memoizable is designed to handle a number of typically problematic aspects
|
||||
of memoization implementations. First a memoizable object can be frozen.
|
||||
|
||||
f = Fibonacci.new
|
||||
f.freeze
|
||||
f.fib(100)
|
||||
|
||||
Memoizable objects are freezable because it does not use an instance variable
|
||||
to store the memoized return values. Instead it uses a global class instance
|
||||
variable in +Memoizable.cache+.
|
||||
|
||||
However, this also means the memoization cache is not preserved when an object
|
||||
is duplicated. If this behavior is desired an aditional module is provided
|
||||
called `Memoizable::Copy`. When the `Memoiazble::Copy` module is included into
|
||||
a class it not only adds the #memoize class methods but also includes
|
||||
an #initialize_copy method, which copies the appropriate cache entries when
|
||||
#dup or #clone is used. Keep this in mind. It is important to call #super if you
|
||||
override the #initialize_copy method in a class that uses `Memoizable::Copy`.
|
||||
|
||||
class MemoExample2
|
||||
include Memoizable::Copy
|
||||
attr_accessor :a
|
||||
memoize :a
|
||||
end
|
||||
|
||||
m = MemoExample2.new
|
||||
m.a = 10
|
||||
m.a #=> 10
|
||||
|
||||
n = m.dup
|
||||
n.a = 20
|
||||
n.a #=> 10
|
||||
|
||||
The `#memoize` method also accepts a few options that can be used to modify
|
||||
the behvior of the memoizaiton. To memoize a method across all objects
|
||||
of the same class and not just per-object, set the `:class` option to true.
|
||||
|
||||
class MemoExample3
|
||||
include Memoizable
|
||||
attr_accessor :a
|
||||
memoize :a, :class=>true
|
||||
end
|
||||
|
||||
m = MemoExample3.new
|
||||
m.a = 10
|
||||
m.a #=> 10
|
||||
|
||||
n = MemoExample3.new
|
||||
n.a #=> 10
|
||||
|
||||
Memoization is indexed based on arguments passed to the method, as can be seen
|
||||
in the Fibonacci example. Methods can also be memoized by name only, forgoing
|
||||
any indexing based on arguments passed. To do this, set the `:arguments`
|
||||
option to +false+.
|
||||
|
||||
class MemoExample4
|
||||
include Memoizable
|
||||
def f(n)
|
||||
n + n
|
||||
end
|
||||
memoize :f, :arguments=>false
|
||||
end
|
||||
|
||||
m = MemoExample4.new
|
||||
m.f(1)
|
||||
m.f(2) #=> 2
|
||||
m.f(3) #=> 2
|
||||
|
||||
Blocks passed to a method normally have only a present vs. non-present effect
|
||||
on the memoization index. In other words the return value of a method will be
|
||||
cached under one key if a block is passed to it and under a different index
|
||||
if not. It doesn't matter if blocks passed in actually differ, since
|
||||
the only way to compare blocks is by their +object_id+ and it is very rare
|
||||
for the exact same block object to be reused.
|
||||
|
||||
Finally, the `:freeze` option can be used to freeze the memoized results.
|
||||
|
||||
class MemoExample5
|
||||
include Memoizable
|
||||
def f(n)
|
||||
(n + n).to_s
|
||||
end
|
||||
memoize :f, :freeze=>true
|
||||
end
|
||||
|
||||
m = MemoExample5.new
|
||||
m.f(1).frozen? #=> true
|
||||
m.f(2).frozen? #=> true
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
= Multiton
|
||||
|
||||
The Multion pattern is essentially the same as the Singleton pattern, but
|
||||
unlike it's fully unique brethren Multiton's are identical if their
|
||||
initialization parameters are identical.
|
||||
|
||||
To demontrate we first need to load the Facets multiton.rb script.
|
||||
|
||||
require 'facets/multiton'
|
||||
|
||||
Now for the various exmples.
|
||||
|
||||
|
||||
== EXAMPLE A - STANDARD USAGE
|
||||
|
||||
class SomeMultitonClass
|
||||
include Multiton
|
||||
|
||||
attr :arg
|
||||
def initialize(arg)
|
||||
@arg = arg
|
||||
end
|
||||
end
|
||||
|
||||
a = SomeMultitonClass.instance(4)
|
||||
b = SomeMultitonClass.instance(4) # a and b are same object
|
||||
c = SomeMultitonClass.instance(2) # c is a different object
|
||||
|
||||
a.assert == b
|
||||
|
||||
x = [a.arg, b.arg].max * 10 + c.arg
|
||||
x.assert == 42
|
||||
|
||||
|
||||
== EXAMPLE B - MODIFY AN EXISTING CLASS
|
||||
|
||||
We can modify an existing class (if we are so bold), for example we can
|
||||
add shared filehandles to the File class.
|
||||
|
||||
class ::File
|
||||
include Multiton
|
||||
end
|
||||
|
||||
lineno = __LINE__
|
||||
# HERE1
|
||||
# HERE2
|
||||
|
||||
a = File.instance(__FILE__)
|
||||
b = File.instance(__FILE__)
|
||||
|
||||
b.assert == a
|
||||
|
||||
lineno.times{ a.gets }
|
||||
|
||||
a.gets.strip.assert == "# HERE1"
|
||||
b.gets.strip.assert == "# HERE2"
|
||||
|
||||
|
||||
== EXAMPLE C - INHERITENCE
|
||||
|
||||
class A < String
|
||||
include Multiton
|
||||
end
|
||||
|
||||
# B is also a multiton - with it's OWN object cache
|
||||
class B < A; end
|
||||
|
||||
# w is the same object as x, y is the same object as z
|
||||
w,x,y,z = A.instance('A'), A.instance('A'), B.instance('B'), B.instance('B')
|
||||
|
||||
x.object_id.assert == w.object_id
|
||||
z.object_id.assert == y.object_id
|
||||
|
||||
a = B.instance('A')
|
||||
b = B.instance('A')
|
||||
|
||||
w.object_id.refute == a.object_id # (each class has it's own pool)
|
||||
b.object_id.assert == a.object_id
|
||||
|
||||
|
||||
== EXAMPLE D - MULTIPLE MODULE INCLUSION (does nothing)
|
||||
|
||||
class A < String
|
||||
include Multiton
|
||||
end
|
||||
|
||||
# B is also a multiton - with it's OWN object cache
|
||||
class B < A; end
|
||||
|
||||
|
||||
# w is the same object as x, y is the same object as z
|
||||
w,x,y,z = A.instance('A'), A.instance('A'), B.instance('B'), B.instance('B')
|
||||
|
||||
yz_id = y.object_id || z.object_id
|
||||
|
||||
If we include Multiton again, it will have to effect.
|
||||
|
||||
B.class_eval {
|
||||
include Multiton
|
||||
}
|
||||
|
||||
# y is not only the same object as z, but they are both the same object(s)
|
||||
# as from EXAMPLE C
|
||||
|
||||
y,z = B.instance('B'), B.instance('B')
|
||||
|
||||
yz_id.assert == y.object_id
|
||||
yz_id.assert == z.object_id
|
||||
|
||||
|
||||
== EXAMPLE E - SO YOU WANNA USE NEW INSTEAD OF INSTANCE
|
||||
|
||||
module K
|
||||
# use an inner class which is itself a multiton
|
||||
class K < String; include Multiton; end
|
||||
|
||||
# define a new which returns a mutltion using #instance...
|
||||
class << self
|
||||
def new(*args, &block)
|
||||
K.instance(*args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
the = K.new '4'
|
||||
answer = K.new '2'
|
||||
|
||||
x = sprintf( "%s%s", the, answer )
|
||||
x.assert == "42"
|
||||
the.class.assert == K::K
|
||||
|
||||
|
||||
== EXAMPLE F - using Klass.multiton_id
|
||||
|
||||
class Klass
|
||||
include Multiton
|
||||
|
||||
def initialize( important, not_important )
|
||||
@important, @not_important = important, not_important
|
||||
end
|
||||
|
||||
def Klass.multiton_id(*args, &block)
|
||||
# we consider only the first arg
|
||||
important, not_important = *args
|
||||
important
|
||||
end
|
||||
end
|
||||
|
||||
a = Klass.instance( :a, :b )
|
||||
b = Klass.instance( :a, :c )
|
||||
c = Klass.instance( :b, :b )
|
||||
|
||||
a.assert == b
|
||||
c.refute == a
|
||||
c.refute == b
|
||||
|
||||
Reference in New Issue
Block a user