How do I get the original numbers?
For example when I type:
r = Rational(2, 10)
# (1/5)
2 and 10 will be changed to 1 and 5:
r.numerator # 1
r.denominator # 5
How do I get 2 & 10 from instance of Rational class(r)?
I monkey-patched Rational class and created new method(Rational_o):
def Rational_o *args
x, y = args
r = Rational *args
r.x = x
r.y = y
r
end
class Rational
attr_accessor :x, :y
end
It works, but is there build-in method or variable(s) where original x & y are stored?
No, there isn't. Reduction is a basic and common way to normalize rational numbers. Why would a rational number keep the original numerator and denominator? It does not make sense.
Your question is like asking "Does a string created by "foo" + "bar" (which becomes "foobar") keep the original substrings "foo" and "bar"? Where are they stored?"
If you really want to keep the original numbers, then a rational number is not what you want, and subclassing Rational is not the right way to go. You should use an array holding a pair of numbers.
Rational numbers get normalized on initialization, so you can not know which numbers where given as original arguments. You can also not subclass Rational to install an initializer of your own, and monkeypatching as you do is not really the optimal way to achieve what you want to achieve (which I think you know).
What you can do is create a proxy around Rational with BasicObject which preserves the original arguments and will not disturb normal operation of the original Rational class
class RationalWithArgumentStore < BasicObject
attr_accessor :original_arguments, :rational
def initialize *args
#original_arguments = args
#rational = Rational *args
end
# Basic Object is a basic object, but defines ==
# so let's overwrite it to route to rational
def == other
#rational == other
end
# Route all unknown method calls to rational
def method_missing meth, *args, &block
#rational.send meth, *args, &block
end
end
def RationalWithArgumentStore(*args)
RationalWithArgumentStore.new(*args)
end
So now you can do
my_rational = RationalWithArgumentStore(2,10)
my_rational.original_arguments #=> [2, 10]
#use it like a normal rational
my_rational * 3
my_rational.truncate
No, there is no such inbuilt private or public method that does what you want to do.
If you really want to store the original numbers inside the instance method, your monkey-patch is definitely one of the ways to do so.
In fact, the method Rational(a,b) is an instance method defined outside the class Rational, kind of like Array() and String() methods.
Related
I'm currently learning the basics of Ruby and OOP in general. From what I've read so far, I can use attr_reader to grab the value of an instance variable but not give it access to be overwritten. However, given the code block below, the end result is not what I intended and the instance variable was completely changed from outside of the class. What would be the best way where I can simply read the value and return the intended changes into another variable instead of overwriting the instance variable itself?
class X
def initialize
#y = [1,2,3,4]
end
attr_reader :y
end
class Z
def initialize
end
def self.change(arr)
arr[1] = 0
arr[2] = 0
return arr
end
end
x = X.new
z = Z.change(x.y)
p z
p x.y
From what I've read so far, I can use attr_reader to grab the value of an instance variable but not give it access to be overwritten.
Yes and no.
attr_reader :y creates a so-called getter which is equivalent to:
def y
#y
end
attr_writer :y creates the corresponding setter:
def y=(value)
#y = value
end
And attr_accessor creates both.
The getter allows you to conveniently access #y from the outside. The setter allows you to re-assign #y.
But even with just a getter, you can still send messages to the object. And if the object is mutable, like your array, you can modify it that way:
x = X.new
x.y #=> [1, 2, 3, 4]
x.y.push(5)
x.y #=> [1, 2, 3, 4, 5]
In the above example, #y is not re-assigned, it still refers the same object. But the message push caused the object to change itself.
What would be the best way where I can simply read the value [...]
There are several options. If you want to prevent modification form the outside, you could return a copy of the original array:
class X
def initialize
#y = [1, 2, 3, 4]
end
def y
#y.dup
end
end
dup creates a shallow copy of the array and returns it. "shallow" means that a new array is created containing the same elements. Any modification from the outside to the array will only affect the copy.
But you could still modify its elements (via messages) and that change would be reflected by both, original and copy.
Fortunately, your array contains integers which are immutable.
However, given the code block below, the end result is not what I intended and the instance variable was completely changed from outside of the class.
No, it wasn't. The object that the instance variable references was changed. That is to be expected: arrays can be changed, and you handed the caller an array, so the caller can obviously change the array.
But the instance variable was not changed: it still points to the exact same object as before.
My boss and my mother call me by different names. But if I shave my beard, both of them will see my clean-shaven face.
Understanding the difference between a thing and the name of the thing is fundamental in programming.
What would be the best way where I can simply read the value and return the intended changes into another variable instead of overwriting the instance variable itself?
The best way is to not expose internal representation in the first place. It's not quite clear from your question how a client is expected to use X (the naming is pretty terrible). So, for example, if clients are expected to iterate over the contents of #y, then you offer them a way to do exactly that and only that.
class X
def initialize
self.y = [1, 2, 3, 4]
end
def each_y(...)
return enum_for(__callee__) unless block_given?
y.each(...)
self
end
private attr_accessor :y
end
See Overriding the << method for instance variables for another example of the same problem and how to solve it.
by using clone: https://ruby-doc.org/core-2.7.2/Object.html#method-i-clone
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.
class X
def initialize
#y = [1,2,3,4]
end
attr_reader :y
end
class Z
def self.change(arr)
arr2 = arr.clone
arr2[1] = 0
arr2[2] = 0
return arr2
end
end
x = X.new
z = Z.change(x.y)
p z
p x.y
My question is: how do I overload an operator on a builtin class (such as Integer.new.+) but only for some cases, depending on the class of the second operand
This is the behaviour I'm looking for:
myObject = myClass.new
1 + myObject #=> special behaviour
1 + 2 #=> default behaviour (3)
For example, in Python I would define a __radd__ method on myClass to override case 1.
I've tried using super but apparently Numeric doesn't have operator methods.
Ideally, what I'm looking for is a way to extract the + method and rename it.
Like this:
class Integer
self.plus = self.+ # you know what i mean, I don't know how else to express this.
# I also know that methods don't work like this, this is just to
# illustrate a point.
def + other
other.class == myClass ? special behaviour : self.plus other
end
end
Thanks for your help
Both approaches posted here so far are a legacy Rails way, which is plain wrong. It relies on the fact that the class has no method called plus and nobody will reopen the class to create a method called plus. Otherwise things will go mad.
The correct solution is Module#prepend:
Integer.prepend(Module.new do
def + other
case other
when Fixnum then special_behaviour
else super(other)
end
end
end)
Yes, you can override the behavior of almost anything in the standard library to achieve an outcome, but that's going to hurt understanding of the code and come back to bite you sometime in the future.
In this particular case, Fixnum#+ is designed to take a numeric value and return a numeric result. If we want to define our own classes to interact with Fixnum#+, we need to understand the design contract and adhere to it.
The general convention in Ruby is to use duck typing. We don't care about the class of the object, we just care whether it behaves like / can be converted to the object we want. Eg:
class StringifiedNumber
def initialize(number)
#number = number
end
# String#+ calls to_str on any object passed to it
def to_str
# replace with number to string parsing logic
"one hundred"
end
end
> "total: " + StringifiedNumber.new(100)
=> "total: one hundred"
Things are a bit more complex with numbers since you may mix integers, floats, complex numbers, etc. The convention to handle this is to define a coerce method which returns two elements of the same type which are then used to perform the requested operation.
class NumberfiedString
def initialize(string)
#string = string
end
def to_i
# replace with complicated natural language parsing logic
100
end
def +(other_numberfied_string)
NumberfiedString.new(self.to_i + other_numberfied_string.to_i)
end
# For types which are not directly supported,
# Fixnum#+(target) will call the equivalent of
# target.coerce[0] + target.coerce[1]
def coerce(other)
[NumberfiedString.new(other.to_s), self]
end
end
> NumberfiedString.new("one hundred") + NumberfiedString.new("one hundred")
=> #<NumberfiedString:0x007fadbc036d28 #string=200>
> 100 + NumberfiedString.new("one hundred")
=> #<NumberfiedString:0x007fadbc824c88 #string="200">
To answer OP's follow up question:
Is there no equivalent to Python's radd and related methods? (Where,
if the first operand doesn't support the operation or the types, the
second operand takes over)
class MyClass
def +(other)
puts "called +"
end
def coerce(other)
[self, other]
end
end
> 1 + MyClass.new
called +
=> nil
I'm programming a board game as part of an Ruby OOP learning exercise. I'm attempting to create a game board consisting of nine spaces arranged in three rows of three. Below is a snippet of the currently non-functioning code:
class Board
def initialize()
#dynamically creates instances of the Space class named #s1 through #s9 with #s 1-9 as the location parameter
9.times do |x|
x += 1
y = "#s" + x.to_s
instance_variable_set( y , x.to_s) = Space.new
end
end
def print_board
#prints a 3X3 grid containing each instance's location parameter
puts "\n\n#{#s1.location}|#{#s2.location}|#{#s3.location}\n-----\n#{#s4.location}|#{#s5.location}|#{#s6.location}\n-----\n#{#s7.location}|#{#s8.location}|#{#s9.location}\n\n\n"
end
end
class Space
attr_accessor :location
def initialize(location)
#location = location
end
end
board = Board.new
board.print_board
The desired output is:
X|X|X
-----
X|X|X
-----
X|X|X
...but I keep getting errors related to the creation of instances of the Space class. I've tried so many different things that I can't remember them all. I know there has to be a way to do it like this, but I'm new to Ruby, and it has me stumped. I know could just create 9 individual instances of the Space class, but that seems cheap and dirty.
Here's a slightly re-worked, more Ruby-style version of same:
class Board
def initialize
#board = Array.new(9) do
Space.new
end
end
def move(x,y,set)
#board[(x - 1) + (y - 1) * 3].location = set
end
def to_s
#board.each_slice(3).collect do |row|
row.join('|')
end.join("\n------\n") + "\n"
end
end
class Space
attr_accessor :location
def initialize(location = nil)
#location = location || ' '
end
def to_s
#location
end
end
board = Board.new
puts board.to_s
board.move(1,2,'x')
puts board.to_s
It's worth noting a few tricks:
Array.new takes an argument as to how many entries to pre-populate with.
An Array can be given a block to evaluate that supplies the value to be used for default entries.
each_slice pulls out groups of N entries to process.
collect transforms each row into a simple string.
join combines the rows with horizontal lines.
to_s is the default name for "show as string" and should be used unless it's otherwise confusing.
Space needed a default of nil for the initializer
Methods that do not take arguments do not need brackets, so initializer is preferable to initializer().
The approach you're pursuing with instance_variable_set is a lot harder to maintain and violates the Zero, One or Infinity Rule by having nine variables that represent the cells. What you need is one variable of theoretically infinite size, bounded only by constraints you impose if necessary. Having multiple variables makes things much more difficult to iterate, makes it inconvenient to pass through as an argument, and generally makes a mess of what should be simple.
I have this code:
def setVelocity (x, y, yaw)
setVelocity (Command2d.new(x,y,yaw))
end
def setVelocity (vel)
......
end
vel is a Command2D class that has 3 attributes, is Comparable and defines + , basically is a convenient class for me to manage those 3 attributes, so I want to use it internally in my library (dont want to make them private, either give them weird names).
But Ruby seems to keep only the last setVelocity even when the number of parameters is different. so when I call setVelocity with 3 parameters will say that I need to call that method with only one parameter.
Ruby doesn't really support overloading.
This page gives more details and a workaround. Basically you create a single method with a variable number of parameters, and deal with them appropriately.
(I'd personally recommend writing one method to recognise the two different "faked overloads" and then one method for each overload, with different names reflecting the different parameters.)
Alternatively, just provide different names to start with :)
Just for comparison, here's how I would solve it:
#!/usr/bin/env ruby
class Command2D
def initialize(x, y, yaw)
#command = [x, y, yaw]
end
end
class Vehicle
def velocity=(command_or_array)
case command_or_array
when Command2D
self.velocity_from_command = command_or_array
when Array
self.velocity_from_array = command_or_array
else
raise TypeError, 'Velocity can only be a Command2D or an Array of [x, y, yaw]'
end
end
private
def velocity_from_command=(command)
#velocity = command
end
def velocity_from_array=(ary)
raise TypeError, 'Velocity must be an Array of [x, y, yaw]' unless ary.length == 3
#velocity = Command2D.new(*ary)
end
end
v1 = Vehicle.new
v1.velocity = Command2D.new(1, 2, 3)
v2 = Vehicle.new
v2.velocity = [1, 2, 3]
p v1
p v2
Use attr_accessor to add attributes and you will get getters and setters automatically.
Alternatively use attr_reader or attr_writer to get read-only or write-only attributes.
class Foo
attr_accessor :velocity
end
You can now set and get the value of this attribute like this:
foo = Foo.new
foo.velocity = 100
puts foo.velocity # => 100
If you want to add methods to set the attribute based on some parameters, use a name reflecting what kind of input are expected:
def velocity_from_yaw(x, y, yaw)
velocity = Command2d.new(x, y, yaw)
end
You can probably find a much better name in this case, but I don't know what your x, y and yaw really mean in your context.
So I understand you aren't supposed to to directly subclass Fixnum, Float or Integer, as they don't have a #new method. Using DelegateClass seems to work though, but is it the best way? Anyone know what the reason behind these classes not having #new is?
I need a class which behaves like a Fixnum, but has some extra methods, and I'd like to be able to refer to its value through self from within the class, for example:
class Foo < Fixnum
def initialize value
super value
end
def increment
self + 1
end
end
Foo.new(5).increment + 4 # => 10
You can pretty easily set up a quick forwarding implementation yourself:
class MyNum
def initialize(number)
#number = number
end
def method_missing(name, *args, &blk)
ret = #number.send(name, *args, &blk)
ret.is_a?(Numeric) ? MyNum.new(ret) : ret
end
end
Then you can add whatever methods you want on MyNum, but you'll need to operate on #number in those methods, rather than being able to call super directly.
IIRC, the main implementation of Ruby stores Fixnums as immediate values, using some of the low bits of the word to tag it as a Fixnum instead of a pointer to an object on the heap. That's why, on a 32-bit machine, Fixnums are only 29-bits (or whatever it is) instead of a full word.
So because of that, you can't add methods to a single "instance" of Fixnum, and you can't subclass it.
If you're dead-set on having a "Fixnum-like" class, you'll probably have to make a class that has a Fixnum instance variable, and forward method calls appropriately.
Could you not extend FixNum itself? Like...
class Fixnum
def even?
self % 2 == 0
end
end
42.even?