programing

루비 패스는 레퍼런스입니까, 아니면 값입니까?

javamemo 2023. 6. 4. 10:15
반응형

루비 패스는 레퍼런스입니까, 아니면 값입니까?

@user.update_languages(params[:language][:language1], 
                       params[:language][:language2], 
                       params[:language][:language3])
lang_errors = @user.errors
logger.debug "--------------------LANG_ERRORS----------101-------------" 
                + lang_errors.full_messages.inspect

if params[:user]
  @user.state = params[:user][:state]
  success = success & @user.save
end
logger.debug "--------------------LANG_ERRORS-------------102----------" 
                + lang_errors.full_messages.inspect

if lang_errors.full_messages.empty?

@user는 개가오추다에 합니다.lang_errors입니다.update_lanugages방법. 저장수할 때@user I 에 object에 .lang_errors변수.

하지만 제가 하려는 것은 (효과가 없는 것처럼 보이는) 해킹에 가까울 것입니다.변수 값이 지워지는 이유를 알고 싶습니다.저는 참조로 전달하는 것을 이해하기 때문에 어떻게 그 변수의 값이 지워지지 않고 유지될 수 있는지 알고 싶습니다.

다른 답변들은 모두 옳지만, 친구가 이것을 그에게 설명해 달라고 부탁했고, 결국 루비가 변수를 다루는 방법이 핵심입니다. 그래서 저는 그에게 쓴 간단한 사진/설명을 공유하려고 생각했습니다(길이에 대한 사과와 아마도 지나치게 단순화된 것일 수도 있습니다).


변수 Q1을 ?str'foo'?

str = 'foo'
str.object_id # => 2000

여기에 이미지 설명 입력

A: 라는레입다니블이▁a▁called라는 라벨.str 점으로 됩니다.'foo'이 루비 인터프리터의 상태에 대해 메모리 위치에 있습니다.2000.


Q2를 ?str를사여새개로체를 하여 새 =?

str = 'bar'.tap{|b| puts "bar: #{b.object_id}"} # bar: 2002
str.object_id # => 2002

여기에 이미지 설명 입력

A: 라벨str이제 다른 개체를 가리킵니다.


Q3를 ?=str?

str2 = str
str2.object_id # => 2002

여기에 이미지 설명 입력

:의 새 입니다.str2동일한 객체를 가리키도록 생성됩니다.str.


Q4에서 : ▁the▁▁object▁if?▁q▁happens까됩니▁q▁what어떻4게4?str그리고.str2바뀌었나요?

str2.replace 'baz'
str2 # => 'baz'
str  # => 'baz'
str.object_id # => 2002
str2.object_id # => 2002

여기에 이미지 설명 입력

A: 두 레이블 모두 동일한 개체를 가리키지만 해당 개체 자체가 변형되었습니다(내용이 다른 개체로 변경됨).


이것이 원래 질문과 어떻게 관련이 있습니까?

하며, . 메소드는 변수/라벨의 자체 복사본을 가져옵니다.str2되는 것 ( ) 그것에전는것 ( )str수 .str 를 가리키지만 둘 다 참조하는 개체의 내용을 변경할 수 있습니다.

str = 'foo'

def mutate(str2)
  puts "str2: #{str2.object_id}"
  str2.replace 'bar'
  str2 = 'baz'
  puts "str2: #{str2.object_id}"
end

str.object_id # => 2004
mutate(str) # str2: 2004, str2: 2006
str # => "bar"
str.object_id # => 2004

전통적인 용어로, Ruby는 엄밀하게는 pass-by-value입니다.하지만 당신이 여기서 요구하는 것은 사실 그게 아닙니다.

Ruby는 순수한 비참조 값에 대한 개념이 없기 때문에 메소드에 전달할 수 없습니다.변수는 항상 개체에 대한 참조입니다.당신 밑에서 변하지 않는 물체를 얻기 위해서는 당신이 전달한 물체를 복제하거나 복제해야 하며, 따라서 다른 사람이 참조할 수 없는 물체를 제공해야 합니다. (하지만 이것도 방탄은 아니지만, 두 표준 복제 방법 모두 얕은 복사를 합니다.)따라서 복제본의 인스턴스 변수는 원본과 동일한 개체를 가리킵니다.ivar에서 참조하는 개체가 변형된 경우에도 동일한 개체를 참조하기 때문에 복사본에 계속 표시됩니다.)

Ruby는 "객체 기준 전달"을 사용합니다.

(Python의 용어 사용)

루비가 "값으로 패스" 또는 "기준으로 패스"를 사용한다고 말하는 것은 도움이 될 만큼 충분히 설명적이지 않습니다.오늘날 대부분의 사람들이 알고 있듯이, 그 용어("가치" 대 "기준")는 C++에서 유래했다고 생각합니다.

C++에서 "pass by value"는 함수가 변수의 복사본을 얻음을 의미하며 복사본에 대한 변경 사항은 원본을 변경하지 않습니다.그것은 물체에 대해서도 마찬가지입니다.개체 변수를 값으로 전달하면 전체 개체(모든 구성원 포함)가 복사되고 구성원을 변경해도 원래 개체의 구성원은 변경되지 않습니다.(값별로 포인터를 전달하면 다르지만 어차피 루비는 포인터가 없어, AFAIK.)

class A {
  public:
    int x;
};

void inc(A arg) {
  arg.x++;
  printf("in inc: %d\n", arg.x); // => 6
}

void inc(A* arg) {
  arg->x++;
  printf("in inc: %d\n", arg->x); // => 1
}

int main() {
  A a;
  a.x = 5;
  inc(a);
  printf("in main: %d\n", a.x); // => 5

  A* b = new A;
  b->x = 0;
  inc(b);
  printf("in main: %d\n", b->x); // => 1

  return 0;
}

출력:

in inc: 6
in main: 5
in inc: 1
in main: 1

C++에서 "참조에 의한 전달"은 함수가 원래 변수에 접근하는 것을 의미합니다.완전히 새로운 리터럴 정수를 할당할 수 있으며 원래 변수에도 해당 값이 할당됩니다.

void replace(A &arg) {
  A newA;
  newA.x = 10;
  arg = newA;
  printf("in replace: %d\n", arg.x);
}

int main() {
  A a;
  a.x = 5;
  replace(a);
  printf("in main: %d\n", a.x);

  return 0;
}

출력:

in replace: 10
in main: 10

인수가 개체가 아닌 경우 Ruby는 C++ 의미의 전달 값을 사용합니다.하지만 Ruby에서는 모든 것이 객체이기 때문에 Ruby에서는 C++ 의미의 값이 지나가지 않습니다.

루비에서 "객체 기준 전달"(파이썬의 용어를 사용하기 위해)은 다음과 같이 사용됩니다.

  • 함수 내부에서 개체의 구성원에게 새 값이 할당될 수 있으며 이러한 변경 사항은 함수가 반환된 후에도 계속 유지됩니다.*
  • 함수 내부에서 변수에 완전히 새로운 개체를 할당하면 변수가 이전 개체에 대한 참조를 중지합니다.그러나 함수가 반환된 후에도 원래 변수는 여전히 이전 개체를 참조합니다.

따라서 Ruby는 C++ 의미에서 "기준으로 전달"을 사용하지 않습니다.그러면 함수 내부의 변수에 새 개체를 할당하면 함수가 반환된 후 이전 개체를 잊어버리게 됩니다.

class A
  attr_accessor :x
end

def inc(arg)
  arg.x += 1
  puts arg.x
end

def replace(arg)
  arg = A.new
  arg.x = 3
  puts arg.x
end

a = A.new
a.x = 1
puts a.x  # 1

inc a     # 2
puts a.x  # 2

replace a # 3
puts a.x  # 2

puts ''

def inc_var(arg)
  arg += 1
  puts arg
end

b = 1     # Even integers are objects in Ruby
puts b    # 1
inc_var b # 2
puts b    # 1

출력:

1
2
2
3
2

1
2
1

이러한 이유로 루비에서 함수 내부의 개체를 수정하지만 함수가 반환될 때 이러한 변경 사항을 잊으려면 복사본을 임시로 변경하기 전에 개체의 복사본을 명시적으로 만들어야 합니다.

루비 패스는 레퍼런스입니까, 아니면 값입니까?

루비는 값별로 표시됩니다.항상.예외 없음.아니요.안 돼, 안 돼요.

다음은 그 사실을 보여주는 간단한 프로그램입니다.

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value

루비는 엄밀한 의미에서 값별로 전달되지만 값은 참조입니다.

이를 " 기준 통과"라고 할 수 있습니다.이 기사는 제가 읽은 것 중 가장 좋은 설명을 가지고 있습니다: http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

기준값 전달은 다음과 같이 간단히 설명할 수 있습니다.

함수는 호출자가 사용한 것과 동일한 메모리의 개체에 대한 참조를 수신하고 액세스합니다.그러나 호출자가 이 개체를 저장하고 있는 상자를 수신하지 않습니다. 값별 전달에서와 마찬가지로 함수는 자체 상자를 제공하고 자체적으로 새 변수를 만듭니다.

결과적인 동작은 참조별 전달과 값별 전달의 고전적인 정의의 조합입니다.

이미 몇 가지 훌륭한 답변이 있지만, 저는 이 주제에 대한 두 개의 권위자의 정의를 올리고 싶지만, 누군가가 그들의 훌륭한 오라일리 책인 루비 프로그래밍 언어에서 권위자 Matz(루비의 창조자)와 David Flanagan이 말한 것이 무엇을 의미하는지 설명해주기를 바랍니다.

[3.8.1부터: 객체 참조]

Ruby에서 메서드에 개체를 전달하면 메서드에 전달되는 개체 참조가 됩니다.개체 자체가 아니며 개체에 대한 참조도 아닙니다.메서드 인수는 참조가 아닌 값으로 전달되지만 전달된 값은 개체 참조입니다.

개체 참조가 메서드에 전달되기 때문에 메서드는 이러한 참조를 사용하여 기본 개체를 수정할 수 있습니다.그런 다음 메소드가 반환될 때 이러한 수정 사항이 표시됩니다.

이 모든 것이 마지막 단락, 특히 마지막 문장까지 이해가 됩니다.이것은 기껏해야 오해의 소지가 있고, 더 심한 혼란을 초래합니다.어떤 식으로든 값별 참조에 대한 수정이 기본 개체를 어떻게 변경할 수 있습니까?

루비 패스는 레퍼런스입니까, 아니면 값입니까?

루비는 간접 참조입니다.항상.예외 없음.아니요.안 돼, 안 돼요.

다음은 그 사실을 보여주는 간단한 프로그램입니다.

def foo(bar)
  bar.object_id
end

baz = 'value'

puts "#{baz.object_id} Ruby is pass-by-reference #{foo(baz)} because object_id's (memory addresses) are always the same ;)"

=> 2279146940 object_id의 (메모리 주소)가 항상 같기 때문에 참조 2279146940 Ruby는 pass-by-reference입니다;)

def bar(babar)
  babar.replace("reference")
end

bar(baz)

puts "some people don't realize it's reference because local assignment can take precedence, but it's clearly pass-by-#{baz}"

=> 어떤 사람들은 지역 배정이 우선될 수 있기 때문에 그것이 참조라는 것을 깨닫지 못하지만, 그것은 분명히 우회적인 참조입니다.

매개 변수는 원래 참조의 복사본입니다.따라서 값은 변경할 수 있지만 원래 참조는 변경할 수 없습니다.

사용해 보십시오.

1.object_id
#=> 3

2.object_id
#=> 5

a = 1
#=> 1
a.object_id
#=> 3

b = 2
#=> 2
b.object_id
#=> 5

식별자 a에는 값 개체 1에 대한 object_id 3이 포함되어 있고 식별자 b에는 값 개체 2에 대한 object_id 5가 포함되어 있습니다.

이제 이렇게 해요.

a.object_id = 5
#=> error

a = b
#value(object_id) at b copies itself as value(object_id) at a. value object 2 has object_id 5
#=> 2

a.object_id 
#=> 5

이제 a와 b 모두 값 개체 2를 나타내는 동일한 object_id 5를 포함합니다.따라서 Ruby 변수에는 값 개체를 나타내는 object_ids가 포함됩니다.

다음을 수행하면 오류도 발생합니다.

c
#=> error

하지만 이렇게 하면 오류가 발생하지 않습니다.

5.object_id
#=> 11

c = 5
#=> value object 5 provides return type for variable c and saves 5.object_id i.e. 11 at c
#=> 5
c.object_id
#=> 11 

a = c.object_id
#=> object_id of c as a value object changes value at a
#=> 11
11.object_id
#=> 23
a.object_id == 11.object_id
#=> true

a
#=> Value at a
#=> 11

여기서 식별자 a는 객체 ID가 23인 값 객체 11을 반환합니다. 즉, object_id 23은 식별자 a에 있습니다. 이제 우리는 메소드를 사용하여 예제를 봅니다.

def foo(arg)
  p arg
  p arg.object_id
end
#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23

argin foo에는 x의 반환 값이 할당됩니다.이는 인수가 값 11에 의해 전달되고, 값 11 자체가 개체인 것이 고유한 개체 ID 23을 갖는다는 것을 분명히 보여줍니다.

이제 이것도 확인해 보십시오.

def foo(arg)
  p arg
  p arg.object_id
  arg = 12
  p arg
  p arg.object_id
end

#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23
#=> 12
#=> 25
x
#=> 11
x.object_id
#=> 23

여기서 식별자 arg는 먼저 11을 참조하기 위해 object_id 23을 포함하고 값 object 12로 내부 할당한 후 object_id 25를 포함합니다.그러나 호출 방법에 사용된 식별자 x에 의해 참조되는 값은 변경되지 않습니다.

따라서 Ruby는 값별로 전달되며 Ruby 변수는 값을 포함하지 않지만 값 개체에 대한 참조를 포함합니다.

원래 값을 변경하기 위해 "바꾸기" 방법을 사용할 필요도 없습니다.해시 값 중 하나를 해시에 할당하면 원래 값이 변경됩니다.

def my_foo(a_hash)
  a_hash["test"]="reference"
end;

hash = {"test"=>"value"}
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"
Two references refer to same object as long as there is no reassignment. 

동일한 개체에 있는 업데이트는 새 메모리가 여전히 동일한 메모리에 있기 때문에 새 메모리에 대한 참조를 만들지 않습니다.다음은 몇 가지 예입니다.

    a = "first string"
    b = a



    b.upcase! 
    => FIRST STRING
    a
    => FIRST STRING

    b = "second string"


a
    => FIRST STRING
    hash = {first_sub_hash: {first_key: "first_value"}}
first_sub_hash = hash[:first_sub_hash]
first_sub_hash[:second_key] = "second_value"

    hash
    => {first_sub_hash: {first_key: "first_value", second_key: "second_value"}}

    def change(first_sub_hash)
    first_sub_hash[:third_key] = "third_value"
    end

    change(first_sub_hash)

    hash
    =>  {first_sub_hash: {first_key: "first_value", second_key: "second_value", third_key: "third_value"}}

루비의 "값별 통과 참조"가 어떻게 작동하는지에 대한 이론에 대한 많은 훌륭한 답변들이 있습니다.하지만 저는 예를 들어 모든 것을 훨씬 더 잘 배우고 이해합니다.이것이 도움이 되기를 바랍니다.

def foo(bar)
  puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
  bar =  "reference"
  puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 80 # <-----
bar (value) after foo with object_id 60 # <-----

메소드를 입력했을 때 보다시피, 우리의 막대는 여전히 "값"이라는 문자열을 가리키고 있었습니다.그런 다음 문자열 개체 "reference"를 새 object_id를 가진 막대에 할당했습니다. 경우 foo 내부의 막대는 범위가 다르며, 메소드 내부에 전달된 내용은 다시 할당되어 String "reference"가 저장된 메모리의 새 위치를 가리킬 때 더 이상 막대에 의해 액세스되지 않습니다.

이제 이와 같은 방법을 생각해 보십시오.유일한 차이점은 메소드 내부에서 수행하는 작업입니다.

def foo(bar)
  puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
  bar.replace "reference"
  puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 60 # <-----
bar (reference) after foo with object_id 60 # <-----

차이를 알아차렸습니까?여기서 우리는 변수가 가리키는 String 객체의 내용을 수정했습니다.메소드 내부에서는 막대의 범위가 여전히 다릅니다.

따라서 방법으로 전달된 변수를 처리하는 방법에 주의해야 합니다.그리고 전달된 변수 in-place(gsub!, replace 등)를 수정하는 경우에는 메서드 이름에 "def foo!"와 같이 bang!로 표시합니다.

추신:

foo 내부와 외부의 "bar"는 "다른" "bar"라는 것을 명심하는 것이 중요합니다.그들의 범위는 다릅니다.메소드 내에서 "bar"를 "club"로 이름을 바꿀 수 있으며 결과는 동일합니다.

메소드 안팎에서 변수가 재사용되는 것을 자주 보는데, 괜찮긴 하지만 코드 가독성을 빼앗고 코드 냄새 IMHO입니다.저는 위의 예에서 제가 했던 것을 하지 않고 오히려 이것을 하는 것을 강력히 추천합니다 :).

def foo(fiz)
  puts "fiz (#{fiz}) entering foo with object_id #{fiz.object_id}"
  fiz =  "reference"
  puts "fiz (#{fiz}) leaving foo with object_id #{fiz.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
fiz (value) entering foo with object_id 60
fiz (reference) leaving foo with object_id 80
bar (value) after foo with object_id 60

루비가 통역됩니다.변수는 데이터에 대한 참조이지만 데이터 자체는 아닙니다.이렇게 하면 다른 유형의 데이터에 동일한 변수를 쉽게 사용할 수 있습니다.

lhs = rhs를 할당하면 데이터가 아닌 rhs에 기준이 복사됩니다.이것은 할당이 rhs에서 lhs로 데이터 복사를 수행하는 C와 같은 다른 언어에서 다릅니다.

함수 호출의 경우 전달된 변수, 예를 들어 x는 함수의 로컬 변수로 복사되지만 x는 참조입니다.그러면 두 개의 참조 복사본이 있으며, 둘 다 동일한 데이터를 참조합니다.하나는 발신자에게, 하나는 기능에 있을 것입니다.

그러면 함수에 할당하면 함수의 x 버전에 새 참조가 복사됩니다. 이 이후 호출자의 x 버전은 변경되지 않습니다.여전히 원본 데이터에 대한 참조입니다.

반대로 x에서 .replace 메서드를 사용하면 루비가 데이터 복사를 수행합니다.새 할당 전에 바꾸기를 사용하면 실제로 호출자는 해당 버전의 데이터 변경도 볼 수 있습니다.

마찬가지로 전달된 변수에 대해 원래 참조가 정확한 경우 인스턴스 변수는 호출자가 보는 것과 동일합니다.개체의 프레임워크 내에서 인스턴스 변수는 항상 호출자가 제공한 값이든 클래스가 전달된 함수에 설정된 값이든 가장 최신의 참조 값을 가집니다.

여기서 '값으로 호출' 또는 '참조로 호출'이 혼동됩니다. 컴파일된 언어에서 '='은 데이터 복사본입니다.이 해석된 언어의 '='은 참조 복사본입니다.이 예에서는 참조를 전달한 다음에 참조 복사본을 전달했는데, 여기서 '='은 참조 복사본을 전달한 원본을 분해한 다음, '='이 데이터 복사본인 것처럼 사람들이 이에 대해 이야기합니다.

정의와 일치하려면 데이터 복사본이므로 '.replace'로 유지해야 합니다.'.replace'의 관점에서 보면 이것은 실제로 참조로 전달되는 것입니다.또한 디버거를 실행하면 변수가 참조인 것처럼 참조가 전달되는 것을 볼 수 있습니다.

그러나 '='을 참조 프레임으로 유지해야 하는 경우에는 실제로 할당될 때까지 전달된 데이터를 볼 수 있으며, 할당 후에는 발신자의 데이터가 변경되지 않은 상태로 유지되는 동안 더 이상 볼 수 없습니다.동작 수준에서 이 값은 전달된 값이 복합 값으로 간주되지 않는 한 값으로 전달됩니다. 단일 할당에서 다른 부분을 변경하는 동안 이 값의 일부를 유지할 수 없기 때문입니다(이 할당으로 인해 참조가 변경되고 원본이 범위를 벗어남).또한 모든 변수와 마찬가지로 개체의 변수가 참조가 되는 사마귀가 발생합니다.따라서 우리는 '가치별 참조'를 전달하는 것에 대해 이야기해야 할 것이고 관련된 설명을 사용해야 할 것입니다.

네, 하지만...

루비는 객체에 대한 참조를 전달하고 루비의 모든 것이 객체이기 때문에 참조에 의해 전달된다고 말할 수 있습니다.

저는 그것이 가치에 의해 지나갔다고 주장하는 여기 게시물에 동의하지 않습니다. 그것은 저에게 현학적이고 시맨틱한 게임처럼 보입니다.

그러나 루비 연산의 대부분은 "상자 밖"을 제공하기 때문에 사실상 동작을 "숨깁니다." - 예를 들어 문자열 연산은 객체의 복사본을 생성합니다.

> astringobject = "lowercase"

> bstringobject = astringobject.upcase
> # bstringobject is a new object created by String.upcase

> puts astringobject
lowercase

> puts bstringobject
LOWERCASE

즉, 루비가 "값에 의해 전달됨"인 것처럼 보이기 때문에 대부분의 경우 원래 개체는 변경되지 않은 상태로 유지됩니다.

물론 자신만의 수업을 설계할 때, 이 행동의 세부 사항을 이해하는 것은 기능적 행동, 메모리 효율성 및 성능 모두에 중요합니다.

언급URL : https://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value

반응형