Передавайте аргументы по ссылке на блок с помощью оператора splat

Кажется, что аргументы копируются при использовании оператора splat для передачи аргументов в блок по ссылке.

У меня есть это:

def method
  a = [1,2,3]
  yield(*a)
  p a
end

method {|x,y,z| z = 0}
#=> this puts and returns [1, 2, 3] (didn't modified the third argument)

Как я могу передать эти аргументы по ссылке? Кажется, это сработает, если я передам массив напрямую, но здесь оператор splat был бы гораздо более практичным, интуитивно понятным и удобным в сопровождении.


person alf    schedule 31.07.2012    source источник


Ответы (4)


  1. В Ruby, когда вы пишете x = value, вы создаете новую локальную переменную x независимо от того, существовала она ранее или нет (если она существовала, имя просто восстанавливается, а исходное значение остается нетронутым). Таким образом, вы не сможете изменить переменную на месте таким образом.

  2. Целые числа неизменны. Поэтому, если вы отправляете целое число, вы никак не можете изменить его значение. Обратите внимание, что вы можете изменять изменяемые объекты (строки, хэши, массивы,...):

    def method
      a = [1, 2, "hello"]
      yield(*a)
      p a
    end
    
    method { |x,y,z| z[1] = 'u' }
    # [1, 2, "hullo"]
    

Примечание: я пытался ответить на ваш вопрос, теперь мое мнение: обновление аргументов в методах или блоках приводит к глючному коду (у вас нет ссылочная прозрачность больше). Верните новое значение и позвольте вызывающей стороне обновить саму переменную, если она того желает.

person tokland    schedule 31.07.2012
comment
Очень ясно, спасибо! Причина использования yield в этом методе состоит в том, чтобы дать вызывающей стороне возможность выполнить некоторую предварительную обработку элементов массива. Мне нравится ваша идея вернуть новые значения. Думаю, я пойду с этим. Спасибо еще раз! - person alf; 31.07.2012

Проблема здесь в знаке =. Это заставляет локальную переменную z быть назначенной другому объекту.

Возьмем этот пример со строками:

def method
  a = ['a', 'b', 'c']
  yield(*a)
  p a
end

method { |x,y,z| z.upcase! }   # => ["a", "b", "C"]

Это ясно показывает, что z совпадает с третьим объектом массива.

Еще один момент: ваш пример является числовым. Fixnum имеют фиксированные идентификаторы; поэтому вы не можете изменить номер, сохраняя тот же идентификатор объекта. Чтобы изменить Fixnum, вы должны использовать = для присвоения переменной нового номера вместо самоизменяющихся методов, таких как inc! (такие методы не могут существовать в Fixnum).

person Sony Santos    schedule 31.07.2012
comment
Это ясно показывает, что аргументы были переданы по ссылке — нет, это не так. Это показывает, что Ruby не является чисто функциональным языком. Это все, что он показывает. В Ruby аргументы всегда передаются по значению. Всегда. Никаких исключений, никаких если, никаких но. - person Jörg W Mittag; 01.08.2012
comment
Я имею в виду, что z не является a[2].dup, но z и a[2] имеют один и тот же object_id (который является своего рода ссылкой на объект). Возможно, ссылка не является правильным словом, поскольку оно может иметь не такое значение, как в других языках (например, указатель). - person Sony Santos; 01.08.2012
comment
Дело в том, что если бы аргументы передавались по ссылке, как вы говорите, то у нас не было бы этого разговора, потому что код ОП работал бы так, как он ожидал, и он бы никогда не задал этот вопрос! Вот короткий фрагмент кода, который вы можете использовать, чтобы определить, передает ли Ruby (или любой другой язык) аргументы по значению или по ссылке: def foo(bar); bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}" - person Jörg W Mittag; 01.08.2012

Да... Массив содержит ссылки для объектов. В вашем коде, когда вы используете yield(*a), тогда в блоке вы работаете с переменными, которые указывают на объекты, которые были в массиве. Теперь найдите пример кода:

daz@daz-pc:~/projects/experiments$ irb
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a.object_id
=> 3
irb(main):003:0> a = 2
=> 2
irb(main):004:0> a.object_id
=> 5

Таким образом, в блоке вы не меняете старый объект, вы просто создаете другой объект и устанавливаете его в переменную. Но массив содержит ссылку на старый объект.

Посмотрите на отладочный материал:

def m
  a = [1, 2]
  p a[0].object_id
  yield(*a)
  p a[0].object_id
end

m { |a, b| p a.object_id; a = 0; p a.object_id }

Выход:

3
3
1
3
person Danil Speransky    schedule 31.07.2012
comment
Ваш пример неверен, потому что число на самом деле не является объектом. Попробуйте 3.object_id или 5.object_id, вы всегда будете получать одни и те же идентификаторы. - person megas; 31.07.2012
comment
Есть волшебство, идентификатор числа всегда «2 * число + 1», как можно получить разные идентификаторы? ;) - person megas; 31.07.2012
comment
Я думаю, что @SperanskyDanil пытается сказать, что оператор знака проходит по ссылке. Таким образом, вопрос ОП содержит неверное предположение. Тот факт, что целые числа имеют фиксированные object_id и являются неизменяемыми, с этим не связан. вы можете использовать строки, и вы получите аналогичные результаты. - person Iuri G.; 31.07.2012

Как я могу передать эти аргументы по ссылке?

Вы не можете передавать аргументы по ссылке в Ruby. Ruby передается по значению. Всегда. Никаких исключений, никаких если, никаких но.

Кажется, это работает, если я передаю массив напрямую

Я очень сомневаюсь в этом. Вы просто не можете передавать аргументы по ссылке в Ruby. Период.

person Jörg W Mittag    schedule 01.08.2012