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.
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
I have a class. Let's call it SomeClass:
class SomeClass
end
Instead of defining instances of this class the normal way, I would like to define them all using a constant:
MyConstant = SomeClass.new
I want to be able to capture the name of the constant that some class was set too, in much the same way that standard ruby classes do with the .class method.
MyConstant.name #-> "MyConstant"
I want to be able to do this to render better error messages from all instances of some class, like so:
class SomeClass
def display_error_message
"Error, some class #{self.name} has a problem"
end
end
MyConstant.display_error_message
#-> "Error, some class MyConstant has a problem"
Any way to accomplish this?
EDIT
Here's an example to clarify what I'm shooting for.
(Enum is the name of the class I'm creating, which is meant to act similar to Swifts 'Enum' type. Basically it sets a predefined list of options (:pepperoni, :sausage, :mushroom) with a raw_value ("Pepperoni", "Sausage", "Mushroom".) Obviously in this example a hash or a simple algorithm for converting a symbol to an UpperCamel case string could work, but in reality the enum class will do a lot more, but this example shows the gist of it.
class Pizza
attr_reader :topping
Toppings = Enum.new do
option(:pepperoni).set("Pepperoni")
option(:sausage).set("Sausage")
option(:mushrooms).set("Mushrooms")
end
def set_topping(symbol)
#topping = Toppings[symbol]
end
end
pizza = Pizza.new
### Happy Case
pizza.set_topping(:pepperoni)
### Sad Case (Error message shown below is what I'm trying to figure out)
pizza.set_topping(:spinach)
#-> Error. enum Toppings has no option spinach
A variable is just a way of referring to an object, and the variable's name is irrelevant. If you say X = Y and Y happens to be a class, then the Y class already has the name "Y", so you can't change that.
As far as Ruby is concerned X and Y are indistinguishable.
If you want to alter the name you can make a subclass even if that subclass doesn't do anything different:
X = Class.new(Y)
X.name
# => "X"
Z = X
Z.name
# => "X"
That way preserves the name properly but only in the context of the initialization. I think Ruby does something sneaky and if a new class is being assigned to a constant it assigns a name, but for ordinary variables it does not:
x = Class.new(Y)
x.name
# => nil
So this is a special case.
The key here is that there's a huge difference between a subclass, which does impact the name, and a variable reference, which doesn't.
There's some other strange stuff going on here as it seems like the class somehow "knows" when it's being assigned to something and if that something is a constant it steals the constant's name for itself:
z = Class.new
z.name
# => nil
Z = z
z.name
# => "Z"
As they say in programming: "Wat?"
Your Enum class could look something like this:
class Enum
def initialize(name, &blk)
#defined_options = {}
#name = name.freeze
instance_eval(&blk)
#defined_options.freeze
end
def [](key)
if #defined_options.key? key
#defined_options[key].value
else
unfound_option = Option.new(#name, key)
raise "Option #{unfound_option} not found."
end
end
def to_s
"#{#name}"
end
def inspect
keys = #defined_options.keys.join(',')
"#<#{self}::{#{keys}}>"
end
class Option
attr_reader :value
def initialize(enum_name, key)
#value_initialized = false
#enum_name = enum_name
#key = key
end
def set(value)
if #value_initialized
raise "Value for #{self} can't be set to #{value} " +
"because it is already initialized to #{#value}"
else
#value_initialized = true
#value = value.freeze
end
end
def to_s
"#{#enum_name}::#{#key}"
end
def inspect
"#<#{self}>"
end
end
private
def option(sym)
unless #defined_options.key? sym
option = Option.new(#name, sym)
#defined_options[sym] = option
end
#defined_options[sym]
end
end
Now you can almost keep the syntax you have in your question, and do the following:
class Pizza
attr_reader :topping
# I had to add the name in the initializer for better error reporting.
Toppings = Enum.new('Toppings') do
option(:pepperoni).set("Pepperoni")
option(:sausage).set("Sausage")
option(:mushrooms).set("Mushrooms")
end
def set_topping(symbol)
#topping = Toppings[symbol]
end
end
pizza = Pizza.new
### Happy Case
pizza.set_topping(:pepperoni)
#=> "Pepperoni"
### Sad Case (Error message shown below is what I'm trying to figure out)
pizza.set_topping(:spinach)
#=> RuntimeError: Option Toppings::spinach not found.
This might give you an idea how to solve this issue. This is just a rough sketch of the class and could probably be tweaked to your needs.
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.
EDIT: SOLVED. Multiple data types were not the culprit; the code below works fine. Apparently, I had a mismatch somewhere else.
I am an amateur programmer, and new to Ruby. I am trying to make a class that can be initialized with multiple datatypes. What I mean is this:
I have, say, a "player class". I want to initialize it by setting the name (a string) and the x and y positions (integers in this case). However, I get an error when I do something like this:
#Player class containing name:string, x:integer, y:integer
class Player
attr_accessor :name, :x, :y
def initialize(name,x,y) #Edit: I forgot to put "def" here in my example. This was not my problem, though.
#name = name
#x = x
#y = y
end
end
#Create player "Luke" at x:5, y:5
player = Player.new("Luke",5,5)
The error seems to be that it is expecting all parameters to be one datatype, such as strings, floats, or integers, etc.
How can I specify different datatypes in my class initialization method?
Your code works fine for me in Ruby 1.9.2p180 in Windows. I even added these lines to inspect the data and they behave as expected:
p player.name # => "Luke"
p player.x # => 5
p player.y # => 5
Please post a simplified test code that exhibits the error, tell us the exact error message you are getting, and tell us what version of Ruby you are using. All of this information should have been in your first post. See http://sscce.org/ .
You're missing the "def" before "initialize" in your example.
You missed def in your code, def is the keyword for defining a method and initialize is the constructor of the class(which will be called when a new instance of the class is created).
#Player class containing name:string, x:integer, y:integer
class Player
attr_accessor :name, :x, :y
def initialize(name,x,y)
#name = name
#x = x
#y = y
end
end
Update:
There is no data type declaration for variables in ruby, that means you don't need to specify(declare) the datatype of variable before using it. Hence following is a valid code,
> var = "Robert George"
=> "Robert George"
> var = "2010"
=> "2010"
Accordingly
> Player.new("Robert George", 10, 12)
=> #<Player:0x999b3a4 #name="Robert George", #x=10, #y=12>
> Player.new(10, 12, "Wrong data")
=> #<Player:0x9976158 #name=10, #x=12, #y="Wrong data">
It works whatever datatype you pass, it is the developers responsibility to make sure that your data is valid.
Ruby enthusiasts! I am trying to write a DSL in ruby and i would like to be able to create some magic methods (not sure that is the most accurate term for what i want).
I would like to be able to do things like the following:
a = [1, 2, 3]
b = 2
(a contains b)
And have it resolve to true or false.
Essentially, how can i define the function "contains" so that it takes an array a and a variable b and performs a.contains?(b), but without all of the associated ruby-specific syntax?
if you want a DSL that doesn't use ruby syntax, you need to write a parser at the very least to perform the transformation (raganwalds rewrite lib might be a starting point, http://github.com/raganwald/rewrite)
That said, you don't want to do this. This is more code to maintain and Ruby has already made a lot of the tough decisions that make writing a language syntax hard. Natural language programming also isn't much easier for nonprogrammers to use as the exactness of the format is the challenging aspect (see applescript for instance).
You can abuse method_missing. The tricky thing is, that you cannot access the blocks local variables directly. You'll have to capture the blocks inner binding somewhere (unfortunately block.binding returns the block's outer binding).
You can run this code:
DSL.new do
a = [1, 2, 3]
b = 2
a contains b
end
With the following:
class DSL
attr_reader :last_binding
def initialize(&block)
set_trace_func method(:trace).to_proc
instance_eval(&block)
set_trace_func nil
end
def trace(event, file, line, id, binding, klass)
if event.to_s == "call" and klass == self.class and id.to_s == "method_missing"
#last_binding ||= #current_binding
set_trace_func nil
else
#current_binding = binding
end
end
def lvars
eval('local_variables', last_binding).map(&:to_s)
end
def method_missing(name, *args)
name = name.to_s
if lvars.include? name
eval(name, last_binding).send(*args.flatten)
else
["#{name}?", *args]
end
end
end
class Array
alias contains? include?
end
The closest thing I could think of would be:
def contains var, useless_symbol, arr
arr.include? var
end
Then you could call it like:
contains b, :in, a
I don't think there is any way to be able to use infix notation in your own functions.