I have a class that represents a collection. I included the Enumerable module into it and defined the method #each, so that I get all its methods.
But the problem is that Enumerable's methods don't keep the same class. So, for example, if my class is named Collection, and if I do Collection#select, I would like that the result's class is also Collection (instead of Array). Is there a way how to achieve this?
Since Enumerable#select is designed to return an array, you need to tell somewhere how to map that to a Collection instance. That means, you explicitly need to define Collection#select. Otherwise Ruby will not know the mapping rule from the original array result of Enumerable#select to a Collection instance.
Unfortunately, Ruby's Collection Operations are not type-preserving. Every collection operation always returns an Array.
For collections like Sets or Trees, this is merely annoying, because you need to always convert them back into the type you want to have. But for example for an infinite lazy stream of all prime numbers, this is catastrophic: your program will either hang or run out of memory trying to construct an infinitely large Array.
Most Collection APIs either eliminate duplicate code or are type-preserving, but not both. E.g. .NET's Collection API mostly eliminates duplicate code, but it always returns the same type: IEnumerable (equivalent to Ruby's Enumerator). Smalltalk's Collection API is type-preserving, but it achieves this by duplicating all Collection Operations in every Collection type.
The only Collection API which is type-preserving yet eliminates duplication is Scala's. It achieves this by introducing the new concept of Collection Builders, which know how to efficiently construct a Collection of a specific type. The Collection Operations are implemented in terms of Collection Builders, and only the Collection Builders need to be duplicated … but those are specific to every Collection anyway.
If you want type-preserving Collection Operations in Ruby, you need to either duplicate all Collection Operations in your own Collection (which would be limited to your own code), or redesign the entire Collection API to use Builders (which would require a major redesign of not only your own code but also the existing Collections including every third-party Collection ever written).
It's clear that the second approach is at least impractical if not impossible. The first approach also has its problems, though: Collection Operations are expected to return Arrays, violating that expectation may break other people's code!
You can take an approach similar to Ruby 2.0's lazy collection operations: you could add a new method preserve_type to your API which returns a proxy object with type-preserving Collection Operations. That way, the departure from the standard API is clearly marked in the code:
c.select … # always returns an Array
c.preserve_type.select … # returns whatever the type of c is
Something like:
class Hash
def preserve_type
TypePreservingHash.new(self)
end
end
class TypePreservingHash
def initialize(original)
#original = original
end
def map(*args, &block)
Hash[#original.map(*args, &block)
# You may want to do something more efficient
end
end
Another way could be to make Collection a proxy for the underlying array:
class Collection
def initialize( items= nil )
#items = items || []
end
def respond_to_missing?(method_name, include_private = false)
Enumerable.instance_methods.include? method_name
end
def method_missing name, *args, &block
if #items.respond_to? name
res = #items.send name, *args, &block
res.kind_of?( Array ) ? Collection.new(res) : res
else
super
end
end
end
in IRB:
col = Collection.new [1,2,3]
=> #<Collection:0x0000010102d5d0 #items=[1, 2, 3]>
col.respond_to? :map
=> true
col.map{|x| x * 2 }
=> #<Collection:0x000001009bff18 #items=[2, 4, 6]>
The following worked for me. I found only the filtering methods needed to be redefined. If we redefine all methods that return Array, this includes collect which should not be redefined.
include Enumerable
def select(&block)
self.class.new(super.select(&block))
end
def reject(&block)
self.class.new(super.reject(&block))
end
Related
Can someone suggest a good, ruby-idiomatic, way of avoiding this?
class Foo
attr_accessor :bar
end
a = {one: 1}
x = Foo.new; x.bar = a
x.bar[:two] = 2
p a #=> {one: 1, two: 2}
I could simply not allow the users of a class to access its attributes, which solves the problem...in this case. (What about passing parameters to a method?) Anyway, avoiding everything but attr_reader, and using that only on non-mutable attributes, doesn't seem very Ruby-ish.
Or, I can just not write any code which mutates values, which appeals, but is not exactly easy to do in Ruby.
I could systematically dup or clone every parameter my class is given -- except that those methods don't work on Nilclass, Fixnums, Symbols, etc -- and worse, responds_to?(:dup) == true for those types. (Also, neither dup nor clone do a deep copy.)
In the example above I modify the bar attribute in the caller, but the problem remains the same if the code is in the class, or if I use a method on the class instead of attr_accessor : If I want a class which can accept a value and do something with it, and if for some reason I have to do that by mutating that value somewhere -- is there an idiomatic way in ruby to ensure that I don't infect the caller with that mutated value?
In Ruby we are supposed not to care about the type of the incoming data very much, but it looks as if I have to care about it quite a lot in order to tell how to make this value I want to mutate safe. If it's a NullObject or a Fixnum or a Symbol it's fine, otherwise I can dup it ... unless I need to deep copy it.
That can't be right, can it?
Edit: After Some More Thought
Sergio is of course right -- sometimes you want this behaviour. Not because using the side effect in your code is a good idea, but because sometimes the class you are passing a message to needs a live reference to an object that might change afterwards.
The only time this behaviour is going to be problematic is when you are passing an Enumerable. If I pass an Array or a Hash, I really don't want the receiver to modify that. So my takeaway is:
Do what Sergio said and code defensively whenever I pass stuff to a receiver, just in case the person who coded it hasn't been careful.
Implement a blanket rule in my own classes: dup all incoming Enumerables.
It is responsibility of the caller to shield itself from code being called. Let's say, you have some command line options parsing code. You got this hash of parameters and you want to do some validation (or something). Now, the validating code was written by some other guy who likes to do things in-place for "efficiency". So it is likely that your hash will be mutated and you won't be able to use it later.
Solution? Pass a copy.
validate_params! Marshal.load(Marshal.dump(params)) # deep copy
Note that in some cases mutation is desirable. So it must be the caller who controls the effect (allows or prevents it).
I would consider using freeze:
class Foo
attr_reader :bar
def bar=(value)
#bar = value.freeze # You may need to freeze nested values too
end
end
a = { one: 1 }
x = Foo.new
x.bar = a
x.bar[:two] = 2
# raises: can't modify frozen Hash
Or if you prefer to not change Foo, freeze the value when assigning:
class Foo
attr_accessor :bar
end
a = {one: 1}
x = Foo.new
x.bar = a.freeze
x.bar[:two] = 2
# raises: can't modify frozen Hash
class VowelFinder
include Enumerable
def initialize(string)
#string = string
end
def each
#string.scan(/[aeiou]/) do |vowel|
yield vowel
end
end
end
vf = VowelFinder.new("the quick brown fox jumped")
vf.inject(:+)
The above code scans for vowels in a string and puts them in a single string using inject.
The thing that I am not able understand is what block yield is calling in the each method. As I understand yield requires a block to work which I am not able to see anywhere.
The ruby doc tells you how it works out
Enumerable
The Enumerable mixin provides collection classes with several traversal and searching methods, and with the ability to sort. The class must provide a method each, which yields successive members of the collection. If Enumerable#max, #min, or #sort is used, the objects in the collection must also implement a meaningful <=> operator, as these methods rely on an ordering between members of the collection.
Since your class VowelFinder includes Enumerable you have to implement each using yield and the rest of the magic of the implementation of Enumerable#inject will make inject(:+) work.
The block is an implementation detail of Enumerable and thus not directly accessible, but created by the inject call.
I have a class called Orders. I want to keep an array of all the "Orders" instances so I can .each map/reduce/or otherwise query them.
Later I would like to Marshal "dump" and "load" the "Orders" array of objects.
My plan is to add each new object created onto an ##all_orders array stack on initialization. Then I can do my .each method on the ##all_orders array, looping through all "orders" objects.
Am I approaching this the right OOP way? (code snippet to get the idea)...
class Orders
##all_orders = Array.new
attr_accessor :order_no, :customer
def initialize(order_no, customer)
#id, #customer = order_no, customer
#order_lines = Array.new
##all_orders << self
end
The proper Object-Oriented way to do this is to have a container you're putting the orders into. This can be a plain-old array or a special container class.
The big problem here is you don't have a proper context for storing the "all orders" data. All orders relevant to what? The entire application? If that's the case you need the concept of an order book or a database to store them.
For example:
class OrderBook < Array
end
order_book = OrderBook.new
order_book << Order.new(...)
Note that I've renamed Orders to Order as that's more accurate, singular name.
Auto-magically adding instances to a container is almost always a recipe for disaster. That sort of thing should be handled using a design pattern like model-controller. It's a huge assumption that you'd want to include it in the global orders pool.
I would consider using dependency injection to pass in a list object to your order instances. Best practice in OOP is to decouple code and try to give each object only one responsibility (the Single Responsibility Principle). This helps keep your code maintainable and easily changeable. The way you're approaching it now, the Order class is doing both order-related stuff AND storage of the list. In my example below, a list object can be passed to the Order object when the order is instantiated and all the order need know is that the list object should respond to the #add method. This way, if the implementation for the list has to change, like making the list object use a Redis store, for example, then you don't need to change both the Order class and the OrderList class. You would only update the OrderList class to deal with Redis in the #add method. Additionally, you no longer have an internal dependency in the Order class for handling list responsibilities, so the order is only doing what it should be.
class OrderList
attr_reader :items
def initialize
#items = []
end
def add(order)
#items << order
end
end
class Order
def initialize(opts)
order_list = opts[:order_list]
order_list.add self
end
end
order_list = OrderList.new
order1 = Order.new order_list: order_list
order2 = Order.new order_list: order_list
puts order_list.list.inspect
I dont know how to set array capacity so my array can store only 3 elements. E.g. if I try to push 4th element, it returns error.
Any ideas?
The default Array class doesn't have that functionality. So, your options are:
Create a separate class that stores the three elements and implement your own methods like push and [].
Subclass Array and override the methods to only allow for three elements, for example:
class ThreeElements < Array
def push(*stuff)
raise 'Already has three elements!' unless length < 3
super
end
end
In my opinion, #1 is the better option, because the default Array interface has too many methods to bother with overriding.
You can create a wrapper class for your array! You can also override the "[]" operator in and check if your index is valid:
def [](i)
# getter
end
def []=(i, v)
# setter
end
In C# you can write an extension method like this:
public static Debt[] Foo(this Debt[] arr, int num)
{
// Do something
}
This would allow you to use Foo() on an array of debts: debts.Foo(3)
Can you do this in Ruby? I know you can write a method that will work on arrays:
class Array
def foo
# blah
end
end
but this works on all types of arrays, not just an array of Debts
Thanks in advance.
the extend method is adding the instance methods to a particular object. so in you case it would be:
class YourClass
def foo
# blah
end
end
debts.extend(YourClass)
debts.foo # now foo method is instance method of debts object
Actually it creates singleton class for debts object and then add to it the method. But you can't use this class, that's why it called "ghost" class
This would be a little tricky because Ruby arrays are not homogeneous. That is, you can store different types of objects inside of an array. That would lead me to a solution where I need to first verify that all objects in the array are of type Debt, and if they are then I can act on the array using foo.
You can continue to open up Array and add the foo method, but maybe you should create a FooArray instead and extend Array. This way you can redefine some methods such as << and push to ensure you only take Debts. Since you know that only Debts can be added to your array, you could call foo() without worry.
I believe you can do this by extending Ruby's Array class or better yet defining your own Array-like class and delegating the selected array methods to the native object.
Stack = Array.extract([
:last,
:push,
:pop,
:size,
:clear,
:inspect,
:to_s
])
Why this is better than the alternative methods is explained here.