Имеет ли значение, каким образом используется строковый метод?
Codeacademy учит, что вы можете объединить несколько методов как таковые:
user_input.method1.method2.method3
Однако в следующем уроке они отображают некоторые методы, подобные этому:
user_input = gets.chomp
user_input.downcase!
Я объединил их:
user_input = gets.chomp.downcase!
Когда я использую это так:
user_input = gets.chomp.downcase!
if user_input.include? "s"
...
Я получаю ошибку "undefined method `include?'"
, Если я изменю его на следующее, оно будет работать нормально:
user_input = gets.chomp
user_input.downcase!
if user_input.include? "s"
...
Я в недоумении. Я обеспокоен, является ли это причудой с их консолью или это то, как я должен делать это в Ruby. Если бы кто-то мог сказать мне, какой путь правильный, я был бы признателен. Если оба правы, это тоже нормально.
4 ответа
Во-первых, если вы еще не до конца понимаете, присваивание значений переменным осуществляется через =
и что вы можете проверить, какой это тип переменной, добавив .class
ни к чему.
Учтите следующее:
name = 'John'
puts name
# => John
puts name.class
# => String
Теперь, во-вторых, следует отметить, что возвращаемые значения ВСЕХ методов ВСЕ различны. Но их всех можно отнести к двум типам:
Методы, которые:
- вернуть себя
- вернуть что-либо кроме себя
Пример для 1.
- методы, которые возвращают себя, что можно сказать методы, которые возвращают объект того же типа, который в нашем конкретном случае, String
name = 'John'
puts name
# => 'John'
puts name.class
# => String
downcased_name = name.downcase
puts downcased_name
# => john
puts downcased_name.class
# => String
deleted_downcased_name = downcased_name.delete('h')
puts deleted_downcased_name
# => jon
puts deleted_downcased_name.class
# => String
# All of the above can be just simplified into:
deleted_downcased_name2 = 'John'.downcase.delete('h')
puts deleted_downcased_name2
# => jon
puts deleted_downcased_name2.class
# => String
Заметить, что deleted_downcased_name
а также deleted_downcased_name2
одинаковы, потому что вы можете обрабатывать цепочечные методы так, как будто вы объединяете в цепочку возвращаемые значения, которые 'John' -> 'john' -> 'jon'.
Пример для 2
- методы, которые возвращают что угодно, кроме себя, что можно сказать методами, которые возвращают другой тип.
В нашем конкретном случае, String's downcase!
возвращает либо String
или же NilClass
(ссылка здесь)
- возврате
String
если строка меняется, или - возврате
nil
если строка уже опущена для начала (без изменений).
или другой метод String: start_with?
(ссылка здесь)
- возврате
true
или жеfalse
Это где цепочка методов не будет работать (вызывает ошибку), когда вы пытаетесь использовать метод String в качестве цепочки к значению nil.
Рассмотрим следующее
name = 'mary'
puts name
# => 'mary'
puts name.class
# => String
downcased_name = name.downcase!
puts downcased_name
# => nil
puts downcased_name.class
# => NilClass
downcased_name.delete('h')
# => This will raise the following error
# NoMethodError: undefined method `delete' for nil:NilClass
Ошибка выше, потому что downcased_name является типом NilClass
где вы ожидаете, что это будет своего рода String
, Таким образом, вы не можете больше цепочки любого метода строки на нем. Вы можете только цепь String
методы на String
тип значения. Точно так же вы можете только цепочку Number
методы на Number
тип значения.
Если вы сомневаетесь, вы всегда можете проверить документацию, чтобы проверить, что делает метод, а также его возвращаемое значение и тип.
Цепочка неразрушающих методов, таких как:
string.chomp.downcase...
имеет преимущество в том, что код лаконичен, но неэффективен, если вас не интересует исходное состояние объекта, и вы просто хотите получить конечный результат, потому что он создает промежуточные объекты во время цепочки.
С другой стороны, применяя деструктивные методы последовательно к одному и тому же объекту:
string.chomp!
string.downcase!
...
более эффективно, если вам не нужно сохранять исходное состояние объекта, но не является кратким.
Объединение методов, которые могут возвращать объект другого класса (особенно nil
) как:
string = gets.chomp!.downcase!...
неправильно, потому что результат может стать nil
в какой-то момент в цепи.
Применяя потенциально nil
Возврат только на последней позиции, как вы сделали:
string = gets.chomp.downcase!
все еще бесполезно, если вы ожидаете string
всегда быть строкой, и может легко привести к ошибке, как вы сделали.
Если вы хотите связать эти методы в своем примере, возможно, вы можете сделать это:
user_input = gets.tap(&:chomp!).tap(&:downcase!)
if user_input.include?("s")
...
Добро пожаловать в Ruby! Хотя ваше обучение в Codeacademy может быть ограничено, вы будете продолжать обращаться к документации по языковым API на протяжении всей своей карьеры. Документация API - это описание того, что язык (или библиотека) делает для вас. В этом случае вы используете downcase! который, как указывает один из комментаторов, не всегда возвращает строку. Когда он не предпринимает никаких действий, он возвращает ноль. Nil - это объект в Ruby (как и все остальное), но "включить?" Метод не определен для nil, что объясняет вашу ошибку. (Это одна из самых распространенных ошибок в Ruby, изучите ее значение.)
Так что, на самом деле, здесь ломается не ваша цепочка методов. Дело в том, что один из промежуточных методов не возвращает значение ожидаемого вами типа (ноль вместо некоторого типа String).
Проблема, с которой вы сталкиваетесь, связана с методом взрыва downcase!
,
Это в основном говорит "изменить исходную строку так, чтобы она была в нижнем регистре". Важной частью является то, что это возвращает nil
, Как таковой вы на самом деле звоните include?
на nil
,
Если вы используете метод не взрыва downcase
вместо этого он говорит "преуменьшить ранее цепочку, но не изменять оригинал". Главное отличие в том, что он возвращает результат, а не nil
,
Вот пример:
str = "ABCD"
str.downcase!
=> nil
str
=> "abcd"
str = "ABCD"
str.downcase
=> "abcd"
str
=> "ABCD" # Note str is still unchanged unless you set str = str.downcase