Это может быть тривиально. Я хочу ссылаться на свойство объекта из списка параметров метода объекта, если это имеет смысл.
Тривиальный пример:
'tags'.ljust([calculated_length, obj.length].max)
где в этом примере obj относится к строке tags
.
Возможно ли это вообще?
Вы можете расширить класс String
, добавив собственный метод:
class String
def my_ljust(length)
ljust([length, self.length].max)
end
end
'tags'.my_ljust(8)
Но не уверен, что это имеет большой смысл, потому что метод ljust
уже не сокращает длину строки.
ссылаться на свойство объекта из списка параметров метода объекта
Хотя вы находитесь внутри аргументов метода, вы все равно находитесь за пределами объекта. В этот момент Ruby не предоставляет ссылку на объект.
Если вы хотите сослаться на один и тот же объект несколько раз, обычно вы просто присваиваете его переменной:
str = 'tags'
str.ljust([8, str.length].max)
#=> "tags "
Если вы не хотите этого делать, есть yield_self, который передает получателя данному блоку и возвращает результат блока:
'tags'.yield_self { |str| str.ljust([8, str.length].max) }
#=> "tags "
Вы можете немного сократить это, ссылаясь на аргумент блока через _1
:
'tags'.yield_self { _1.ljust([8, _1.length].max) }
#=> "tags "
@ToddA.Jacobs, если я правильно понял ваш комментарий, для этого и нужны instance_eval
и instance_exec
.
@Stefan Стефан Ты не можешь; это была моя точка зрения. Я предлагал улучшение вашего ответа, которое распаковывает что-то, скрытое псевдокодом в ОП. Вместо этого я решил перенести это в отдельный ответ.
Технически говоря, эту «ссылку на свойство объекта из списка параметров метода объекта» можно интерпретировать как def str.ljust(n,pad=' ',lower_limit=self.length) = super([n,lower_limit].max,pad)
, что является действительным Ruby. Можешь даже поставить self
экземпляр_eval {|obj| блок } → объект
Оценивает строку, содержащую исходный код Ruby или заданный блок, в контексте получателя (obj). Чтобы установить контекст, переменной
self
присваивается значение obj во время выполнения кода, что дает коду доступ к переменным экземпляра obj и частным методам.
Итак, в этом случае ваш код можно изменить на:
'tags'.instance_eval { ljust([8, length].max) }
#=> "tags "
# As the docs mention you can also pass a string
'tags'.instance_eval("ljust([8,length].max)")
#=> "tags "
Поскольку контекст выполнения является получателем, неявным self
будет 'tags'
, хотя этот объект также передается как параметр блока, поэтому вы можете получить ту же семантику, что и другой ответ.
'tags'.instance_eval { |str| str.ljust([8, str.length].max) }
Поскольку этот метод принимает блок, он также соответствует стандартам блоков, связанным с областью видимости переменных, поэтому следующие методы работают одинаково.
padding_length = 8
'tags'.instance_eval { ljust([padding_length, length].max) }
#=> "tags "
Обратите внимание, что из-за контекста выполнения доступны как переменные экземпляра, так и частные методы, и поэтому это следует учитывать при использовании этого метода.
В качестве альтернативы, с теми же оговорками, вы также можете использовать instance_exec
. instance_exec
обеспечивает дополнительное преимущество передачи аргументов для передачи в блок, например:
pad_me = ->(padding_length: 8) { ljust([padding_length, length].max) }
'tags'.instance_exec(&pad_me)
#=> "tags "
'tags'.instance_exec(padding_length: 10, &pad_me)
#=> "tags "
Это интересно. Вы правы в том, что литерал технически является экземпляром String, хотя с точки зрения области видимости он сам по себе не является переменной экземпляра. Это определенно полезная техника!
@ToddA.Jacobs правильно, «буквальный» - это просто синтаксический сахар для создания экземпляра. Что мне нравится в Ruby, так это простота. В Ruby есть только три основные вещи: ключевые слова, экземпляры и методы. Очень просто, но уровень сложности, который можно создать и был создан с помощью этих базовых инструментов, огромен.
Я бы сказал четыре, потому что «выражения». Но по сути вы правы, и я согласен: его общая последовательность (за исключением нескольких мелких и неясных бородавок) — вот почему я тоже люблю Ruby!
«Выражения» @ToddA.Jacobs — это реализации синтаксического анализатора, например присваивание, а операторы — это просто сахар метода.
Стефан дал отличный практический ответ, но не объяснил, почему ваш текущий код не работает. Я попытаюсь сделать это, чтобы дополнить его ответ немного большим пониманием того, как работает Ruby. Используя ваш пример, но изменив object.length
на obj.length
, чтобы избежать путаницы с классом Object, вы приблизительно описали проблему:
# +obj+ refers to the string 'tags'
calculated_length = 2
'tags'.ljust([calculated_length, obj.length].max)
Это фактически вызывает объект Ruby Exception:
неопределенная локальная переменная или метод obj
Это происходит потому, что в Ruby почти все является методом и почти все является выражением, возвращающим значение. Поскольку вы не определили ничего для вызова метода String#length при вызове obj.length
, это вызывает исключение NameError, поскольку obj на этом этапе оценки кода не определен. В зависимости от того, как вы определяете расчетную длину, у вас могут возникнуть те же проблемы, но текущее исключение возникает до того, как вы увидите эту ошибку.
В приведенном выше коде, если вы замените второй аргумент на String#ljust (т. е. obj.length
) на определенную переменную или предварительно вычисленное целочисленное значение, то он будет работать без ошибок. Например:
# pass an Integer literal as your second argument
calculated_length = 2 + 1
"tags".ljust([calculated_length, 8].max)
#=> "tags "
# give the :length method something defined as a receiver
calculated_length = 32 / 4
str = "tags"
str.ljust([calculated_length, str.length].max)
#=> "tags "
Оба работают нормально. На самом деле это не проблема масштаба; просто когда вы используете строковый литерал, используемый вами список аргументов не имеет возможности определить, что исходный литерал предназначен быть получателем, когда вы вызываете String#length в своем списке аргументов.
Либо установив String как переменную, которая будет определена внутри списка аргументов, либо передав литерал в качестве аргумента блоку с помощью Kernel#yield_self (или его псевдонима Kernel#then), вы можете обойти эту проблему. фундаментальная проблема синтаксиса. например:
# pass your String literal into a block; technically,
# "tags" passes _itself_ into the block as positional
# block argument +_1+
"tags".then { _1.ljust([8, _1.length].max) }
#=> "tags "
# assign your values to an instance or local variable
# so they won't be undefined inside your block or the
# argument lists
calculated_length = 2**3
str = "tags"
# using argument lists
str.ljust [calculated_length, str.length].max
#=> "tags "
# block form using same values defined above
"tags".then { _1.ljust [calculated_length, str.length].max }
#=> "tags "
Кроме того, хотя это может показаться менее элегантным, вы можете рассмотреть возможность рефакторинга для удобства чтения и тестирования, а не для компактности. Читабельность, безусловно, зависит от наблюдателя, но тестируемость часто значительно повышается за счет наличия хорошо названных промежуточных и объясняющих переменных, которые можно проверять поэтапно. Например, лично я считаю, что следующий однострочный метод легче читать и отлаживать, чем текущее выражение Array, даже без документации YARD. Если хотите, есть и другие способы написать этот метод еще более явно.
# A one-line "endless method" tested with Ruby 3.4.4.
#
# @note The YARD documentation for this method and
# its examples is 29 lines, longer than the single
# line required to define it!
#
# The method name may seem oddly truncated, but makes
# more sense when you verbalize it as "custom padded
# string". Likewise, using more traditional signature
# like `custom_padded_str str` would be duplicative.
# Naming things in a way that communicates your
# authorial intent is always a hard problem!
#
# @example a String variable with default padding
# str = "foo bar"
# custom_padded str
# #=> "foo bar "
# @example a String var with custom padding expression
# str = "foo bar"
# custom_padded str, 4**2
# #=> "foo bar "
# @example passing just String and Integer literals
# custom_padded "string literal", 20
# #=> "string literal "
#
# @param str [String] required positional argument
# @param padding [Integer] a pre-calculated value
# for padding the String; defaults to +8+
# @return [String] a left-justified padded string
def custom_padded(str, padding=8) = str.ljust(padding)
Я считаю, что это семантически более понятно и для него проще писать модульные тесты, но вы можете этого не делать. Моя основная причина рефакторинга такого типа заключается в том, что встраивание оцененных выражений в вызов String#ljust и особенно использование Array#max внутри вызова метода является частью того, что скрывает исходную ошибку, с которой вы столкнулись. В любом коде могут быть ошибки, но наличие пошагового кода, в котором значения и отдельные строки можно отлаживать с помощью Kernel#puts или debug gem, когда вы сталкиваетесь с проблемами, может избавить вас от многих душевных страданий.
Даже если вам не нравится мой конкретный подход к рефакторингу кода, перенос части вашей логики из вызова метода в объясняющие переменные, на мой взгляд, помог бы быстрее выявить основную причину. Ваш опыт работы со стилями кодирования, безусловно, может отличаться.
Исправление базовых классов Monkey может быть рискованным. Если вы собираетесь это сделать, вам, вероятно, следует хотя бы убедиться, что это либо уточненный метод, либо метод экземпляра. Вероятно, вы ничего не запутаете с помощью :my_lтолько для этого варианта использования, но на самом деле вы не можете быть в этом уверены, если сначала не определите, есть ли
String.respond_to? :my_ljust
, прежде чем (пере)определять его. Ответ практичен и является хорошим быстрым решением, но, по моему собственному опыту, исправление обезьяньих базовых классов всегда имеет ненулевой риск.