def visualization (name, age, looks)
age.to_s
puts name + ' is ' + age + ' years old and looks ' + looks
end
visualization (name = 'Mary', age = 5, looks = 'bad')
#=> `visualization': wrong number of arguments (given 1, expected 3) (ArgumentError)
So the main problem here is the space between visualization and its arguments. When ruby sees visualization (name = 'Mary', age = 5, looks = 'bad') it recognizes `visualization as a method (because it has argument), but it first tries to calculate the expression in the brackets:
(name = 'Mary', age = 5, looks = 'bad')
This on its own is a valid ruby expression, but it might be quite a surprise to see how ruby interpretes it:
name = ('Mary', (age = 5), (looks = 'bad'))
Resulting in name being ['Mary', 5, 'bad'], age being 5 and looks being 'bad'.
As the expression is just an assignment, it returns the assigned value (array ['Mary', 5, 'bad']) which is then passed to your visualisation method. Since you are passing a single array and your method expects three arguments, you're getting the error.
AS per solution, just drop the space between method call and its arguments. You also don't need to name the arguments - this is not Python - here keywords are defined explicitly and you actually creating local variables by doing so.
There are some other issues there as well, but #Stefan has covered that already.
age.to_s doesn't alter the age object (you can't change the class of an object in Ruby), it merely returns a new string.
So you either have to re-assign age via age = age.to_s or move the to_s call to where it is needed:
def visualization(name, age, looks)
puts name + ' is ' + age.to_s + ' years old and looks ' + looks
end
You can use string interpolation instead which is more idiomatic and makes to_s calls superfluous:
def visualization(name, age, looks)
puts "#{name} is #{age} years old and looks #{looks}"
end
When calling this method, you pass the arguments via:
visualization('Mary', 5, 'cute')
# ^ and no space here
The assignments in your code don't make much sense. You probably wanted to have keyword arguments: (note the colons)
def visualization(name:, age:, looks:)
puts "#{name} is #{age} years old and looks #{looks}"
end
visualization(name: 'Mary', age: 5, looks: 'cute')
TL;DR
Ruby isn't whitespace sensitive in the same way as languages like Python, but there are places where whitespace is confusing to the parser. In addition, while some of your code works, it's not idiomatic (it reads like JavaScript) and is less performant. Finally, in-place operators are usually bang methods, so you need to distinguish between methods that change the receiver and ones that return a new result.
Idiomatic Alternative
I changed some of your code for cultural reasons, but kept it close to your intent data-wise. The rest is intended to show how to do what you want more idiomatically.
Here's a more idiomatic and parser-friendly alternative that uses string interpolation:
def user_data(name, age, status)
pp "name is #{name}; age is #{age}; status is #{status}"
end
String interpolation using the embedded expression operator #{expr} within double quotes is often more efficient, and implicitly converts values to strings if it supports a #to_s or #to_str method. The Kernel#p and Kernel#pp methods ensure that the method returns a value, as well as printing to $stdout.
Alternatively, you can also use other constructs like here-documents, String#sprintf, String#format, and many others to construct printf-style interpolations, but the example above is easiest to read for most Rubyists, and should generally be your go-to if you follow most of the popular Ruby style guides, including RuboCop's default style guide.
Note: Interpolation Works with Frozen String Literals
As a final note, this type of interpolation will also work even if you have the frozen-string literal "magic comment" enabled at the top of your file, e.g.:
#!/usr/bin/env ruby
# frozen_string_literal: true
which is often considered a best practice by RuboCop and other similar tools.
Related
I'm practicing a Ruby program for 2D coordinate operations. Among them, the writing of def +(other) and def -(other) confuses me. I have the following three questions:
Why is the method name equal to the operator name?
Where are the parameters received? Passed from where?
Why is the parameter other called other, and what is its value and
transmission process?
This is code
class Point
attr_accessor :x, :y
def initialize(x=0, y=0)
#x, #y = x, y
end
def inspect # p印出(x, y)
"(#{x}, #{y})"
end
def +(other)
self.class.new(x + other.x, y + other.y)
end
def -(other)
self.class.new(x - other.x, y - other.y)
end
end
point0 = Point.new(3, 6)
point1 = Point.new(1, 8)
p point0
p point1
p point0 + point1
p point0 - point1
it will eventually print
(3, 6)
(1, 8)
(4, 14)
(2, -2)
Why is the method name equal to the operator name?
Why not? Why should the symbol used to define the operator not be the symbol used to call it? That's how we do it for every other method, after all: if I want to call foo, I define def foo.
The Language designers could have chosen any arbitrary name. But it just makes sense to have the name of the method be the same symbol that you use to call it. Can you imagine the confusion if the language designers had chosen, for example, the symbol - for the name of the method which corresponds to the + operator?
Other programming languages make different choices. For example, in C++, the name of the function corresponding to the + operator is operator+. In Python, the name of the method corresponding to the + operator is __add__. (More precisely, when encountering the expression a + b, Python will first try calling a.__add__(b) and if that is not implemented by a, then it will try the "reverse add" b.__radd__(a).)
In C#, there is no method name which corresponds to the operator, rather, there is an operator keyword followed by the symbol corresponding to the operator.
If you want to know all the nitty-gritty details about how operator expressions are evaluated in Ruby, I recommend checking out Section 11.4.3 Unary operator expressions and Section 11.4.4 Binary operator expressions of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification.
Where are the parameters received? Passed from where?
It's not quite clear what you mean by this. A parameter is kind of like a "hole" or a placeholder in the method definition. This "hole" then gets filled in with an argument when you actually call the method. For example, here:
def foo(a, b) a + b end
a and b are parameters (more precisely, mandatory positional parameters, they are mandatory because you have to pass an argument for them and they are positional because which argument gets bound to which parameter in the parameter list depends on the position of the argument in the argument list). You don't actually know what the result of this method will be because you don't know what a and b are. They are placeholders for values that will be filled in when you call the method:
foo(2, 3)
Here, 2 and 3 are arguments (more precisely, positional arguments). The argument 2 gets bound to the parameter a because 2 is the first positional argument in the argument list and a is the first positional parameter in the parameter list. The argument 3 gets bound to the parameter b because 3 is the second positional argument in the argument list and b is the second positional parameter in the parameter list.
So, the code that gets ultimately executed for this particular method call is (replacing a with 2 and b with 3):
2 + 3
Please note: this is a simplified explanation. The mental model of replacing every occurrence of the parameter in the method definition body with the argument expression is a good first approximation, but it is not actually what Ruby does. In particular, that mental model I just described corresponds to the call-by-name evaluation strategy, whereas Ruby actually uses a special case of the call-by-value evaluation strategy called call-by-object-sharing.
You can observe the difference in this code:
def bar(a) a + a end
bar((puts "Hello"; 23))
# Hello
#=> 46
In the "replace every occurrence of the parameter with the argument expression" mental model which corresponds to call-by-name, the code would look like this:
(puts "Hello"; 23) + (puts "Hello"; 23)
# Hello
# Hello
#=> 46
and Hello would be printed twice.
However, with call-by-value and call-by-object-sharing, the argument expression gets evaluated before calling the method and the result of that evaluation gets passed in, so the actual code execution looks more like this:
__fresh_variable_with_unspeakable_name__ = (puts "Hello"; 23)
# Hello
__fresh_variable_with_unspeakable_name__ + __fresh_variable_with_unspeakable_name__
#=> 46
If you want to know all the nitty-gritty details about how method arguments are evaluated in Ruby, I recommend checking out Section 11.3 Method invocation expressions, in particular Section 11.3.1 General description and Section 11.3.2 Method arguments of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification.
There is also some (unfortunately incomplete) information to be found in The Ruby Spec Suite aka ruby/spec, in particular in language/def_spec.rb and language/method_spec.rb.
Why is the parameter other called other, and what is its value and
transmission process?
The parameter other is called other because that is what the author of that piece of code chose to call it. They could have called it a or b or x or y or foo or bar or i_dont_have_the_slightest_idea_what_to_call_this_parameter_so_i_am_just_choosing_something_totally_random. If you want to know why the author of that piece of code chose to call it other, you would have to ask the author of that piece of code.
other is a somewhat popular name for the "other" operand of a binary operator definition, not just in Ruby, as you can see for example in the Python documentation as well. It does make sense if you read it out loud: in an Object-Oriented Programming Language like Ruby or Python, where operators are interpreted as being sent to one of the operands, one of the two operands of a binary operator will always be self or this and the "other" operand will be … well … the "other" operand. So, naming it other is just natural. In Ruby, this is codified in some Style Guides, for example the Rubocop Ruby Style Guide.
Some programmer made a method that gets lots of arguements like this:
def age_entry(age_1, age_2, age_3, age_4, age_5, age_6, age_7, age_8)
end
They could pass an array but simply they didn't. I love automation and hate to repeatedly add these variables to and array like this
ages = [age_1, age_2, age_3 ,..., age_8]
I would like to use metaprogramming or other ways to loop with a for or each methods to add them variables to an array like this:
(1..8).each do |index| do
ages << "age_" + index #value of age_[index] get saved to ages
end
P.S. I know I can use copy and paste but this is only for doing automation stuff with Ruby.
"Some programmer" should remember that you can pass in arrays. This sort of method signature is really obnoxious to work with for a multitude of reasons, some of them you've already discovered.
One way to refactor this method and preserve functionality is to just take in varargs:
def age_entry(*ages)
end
Now those values are put in an array for you but you can call the method the same way as before. As a plus you can specify more or fewer entries.
Variables with names like x1, x2 and so on are violations of the Zero, One or Infinity Rule and are a sign you need to think about the problem differently.
You don’t need any metaprogramming here. Just splat them:
ages = [age_1, age_2, age_3 ,..., age_8]
# ⇓ HERE
age_entry(*ages)
If you want to collect age_(1..8) into the array, assuming all local vars are defined, use Kernel#binding:
b = binding
ages = (1..8).map { |i| b.local_variable_get("age_#{i}") }
Suppose the method is as follows.
def oldest(age_bill, age_barb, age_trixie)
puts "Barb is #{age_barb} years old"
[age_bill, age_barb, age_trixie].max
end
oldest(35, 97, 29)
#=> 97
As well as the calculation in the penultimate line (which the OP wishes to avoid), this method requires knowledge of an individual method argument (age_barb). The following is one way to accomplishing both requirements.
def oldest(age_bill, age_barb, age_trixie)
puts "Barb is #{age_barb} years old"
b = binding
args = method(__method__).parameters.map { |arg| b.local_variable_get(arg[1]) }
args.max
end
#=> 97
Barb is 97 years old
Here
args
#=> [35, 97, 29]
I am reading through Chris Pine's Learn To Program chapter 7 Arrays and Iterators.
He introduces the each method with the following example:
languages = ['English', 'German', 'Ruby']
languages.each do |lang|
puts 'I love ' + lang + '!'
puts 'Don\'t you?'
end
puts 'And let\'s hear it for C++!'
puts '...'
It's not hard to understand how it works overall, but I can't figure out where the |lang| part is coming from so out of blue. Shouldn't it be assigned/named or something before it can be used like this? So the computer can know what the "lang" refers to? Does || do something wrapping around lang? Or does ruby just know what lang means?
I am afraid the question is too basic, but I am hoping someone might help me just a bit...
lang is a variable used to hold an element from the languages array. Any variable inside || will be used to grab single element from array. So, every time the loops executes, an element from the array is popped out and held in an variable named lang and data held by lang is displayed using puts method.
The each method yields every element one by one and it gets assigned to the variable lang.
Internally, the each method is implemented something like this:
def each
index = 0
while index < array.length
yield array[index]
index += 1
end
end
|lang| is a block variable. If you strip down your code, you can see that the .each method is iterating over the languages array and assigning array elements to the block variable:
languages = ['English', 'German', 'Ruby']
languages.each do |lang|
puts lang
end
#=> English
#=> German
#=> Ruby
Multi-line blocks use a do/end syntax (as in your example), and single-line blocks use a braces syntax. For example:
languages = ['English', 'German', 'Ruby']
languages.each { |lang| puts lang}
It sounds like, in the above example, you created an array storing multiple language variables.
You then iterated over all three elements in the array and represented each one with a variable called lang.
lang, which is inside the brackets is simply a variable.
Hope this helped you
I have been reading a Ruby book where I encountered below code to describe symbols
def walk(direction)
if direction == :north
# ...
end
end
I tried and failed to create a similar method ( where a comparison is made against a symbol such as
direction == :north
because most of the time I have seen symbols being used something like param[:name], so in my code I tried :north = 1 or :favourite = 'ruby' but got syntax error.
Is it really possible to have such a comparison using a symbol alone (without hash) ie instead of
if "ruby" == param[:name]
end
if "ruby" == :name
end
I am not sure if I have expressed the question clearly, if not I shall try and reword it.
I see a misunderstanding of what symbols are and what is their purpose.
if direction == :north
In this line, direction is a variable (it can hold any value) and :north is a value (it can be assigned to variables).
Trying to do this:
:north = 1
is like trying to do
2 = 1
Which doesn't make sense, of course.
Symbols are rather like identifiers, or a special version of strings.
With strings, you can have
str1 = 'SYM'
and
str2 = 'symbol'
str2 = str2[0,3].upcase
and now there are two identical strings in different places in memory. Ruby has to compare all the characters to evaluate str1 == str2.
However symbols are unique - you can't do character manipulation on them, and if you have
sym1 = :SYM
sym2 = :SYM
then it takes only a single comparison to test their equality. It demonstrates this clearly if we look at the object IDs for the strings and the symbols
puts str2.object_id
puts str1.object_id
puts sym1.object_id
puts sym2.object_id
puts str1.to_sym.object_id
puts str2.to_sym.object_id
output
22098264
22098228
203780
203780
203780
203780
So the two strings have different object IDs, whereas the two symbols are, in fact, the same object. Even converting the two strings to symbols gives the same object ID, so there is only one :SYM.
Because symbols are values, it makes no sense to write something like :north = 1 as it is like writing 'north' = 1.
Comparing strings to symbols, like 'north' = :north will always return false, because they are different classes of object.
param[:name] works only because you can index a hash with any object. (You can say param[Object.new] = 1.) It's different from writing either param['name'] (indexing the hash by a literal string) or param[name] (indexing the hash by the contents of variable name).
Does this answer your question?
Is it possible to define a block in an inline statement with ruby? Something like this:
tasks.collect(&:title).to_block{|arr| "#{arr.slice(0, arr.length - 1).join(", ")} and #{arr.last}" }
Instead of this:
titles = tasks.collect(&:title)
"#{titles.slice(0, titles.length - 1).join(", ")} and #{titles.last}"
If you said tasks.collect(&:title).slice(0, this.length-1) how can you make 'this' refer to the full array that was passed to slice()?
Basically I'm just looking for a way to pass the object returned from one statement into another one, not necessarily iterating over it.
You're kind of confusing passing a return value to a method/function and calling a method on the returned value. The way to do what you described is this:
lambda {|arr| "#{arr.slice(0, arr.length - 1).join(", ")} and #{arr.last}"}.call(tasks.collect(&:title))
If you want to do it the way you were attempting, the closest match is instance_eval, which lets you run a block within the context of an object. So that would be:
tasks.collect(&:title).instance_eval {"#{slice(0, length - 1).join(", ")} and #{last}"}
However, I would not do either of those, as it's longer and less readable than the alternative.
I'm not sure exactly what you're trying to do, but:
If you said tasks.collect(&:title).slice(0, this.length-1) how can you make 'this' refer to the full array that was passed to slice()?
Use a negative number:
tasks.collect(&:title)[0..-2]
Also, in:
"#{titles.slice(0, titles.length - 1).join(", ")} and #{titles.last}"
you've got something weird going on with your quotes, I think.
I don't really understand why you would want to, but you could add a function to the ruby classes that takes a block, and passes itself as a parameter...
class Object
def to_block
yield self
end
end
At this point you would be able to call:
tasks.collect(&:title).to_block{|it| it.slice(0, it.length-1)}
Of course, modifying the Object class should not be taken lightly as there can be serious consequences when combining with other libraries.
Although there are many good answers here, perhaps you're looking for something more like this in terms of an objective:
class Array
def andjoin(separator = ', ', word = ' and ')
case (length)
when 0
''
when 1
last.to_s
when 2
join(word)
else
slice(0, length - 1).join(separator) + word + last.to_s
end
end
end
puts %w[ think feel enjoy ].andjoin # => "think, feel and enjoy"
puts %w[ mitchell webb ].andjoin # => "mitchell and webb"
puts %w[ yes ].andjoin # => "yes"
puts %w[ happy fun monkeypatch ].andjoin(', ', ', and ') # => "happy, fun, and monkeypatch"