할당

할당 (Assignment)

Ruby에서 할당은 =(등호) 문자를 사용해요. 다음 예시는 숫자 5를 지역 변수 v에 할당하죠.

v = 5

할당은 변수가 이전에 참조된 적이 없으면 지역 변수를 새로 만들어요. 할당 표현식의 결과값은 항상 할당된 값이에요. 할당 메서드(assignment method)를 쓸 때도 마찬가지예요.

출처: Ruby 공식 문서

본문

지역 변수 이름 (Local Variable Names)

지역 변수 이름은 소문자 US-ASCII 문자나 8번째 비트가 설정된 문자로 시작해야 해요. 보통 지역 변수는 US-ASCII 호환인데, 그 글자들을 타이핑할 키가 모든 키보드에 있기 때문이에요.

(Ruby 프로그램은 US-ASCII 호환 문자셋으로 작성해야 해요. 그런 문자셋에서 8번째 비트가 설정되어 있으면 확장 문자를 뜻해요. Ruby는 지역 변수에 그런 문자를 포함시키는 것을 허용해요.)

지역 변수 이름에는 문자, 숫자, _(밑줄 또는 low line), 또는 8번째 비트가 설정된 문자가 포함될 수 있어요.

지역 변수 스코프 (Local Variable Scope)

지역 변수 이름이 한 번이라도 할당에 쓰이면, 그 스코프의 나머지에서 그 이름의 모든 사용은 지역 변수로 취급돼요.

다음 예시를 볼게요.

1.times do
  a = 1
  puts "local variables in the block: #{local_variables.join ", "}"
end

puts "no local variables outside the block" if local_variables.empty?

이 코드는 이렇게 출력해요.

local variables in the block: a
no local variables outside the block

블록은 새로운 스코프를 만들기 때문에, 블록 안에서 만든 지역 변수는 바깥 스코프로 새어 나가지 않아요.

바깥 스코프에서 정의한 변수는 안쪽 스코프에도 보여요.

a = 0

1.times do
  puts "local variables: #{local_variables.join ", "}"
end

이 코드는 이렇게 출력해요.

local variables: a

블록 인자에 ;를 붙여 변수 목록을 나열하면, 그 변수들을 블록 안에서 바깥 스코프로부터 격리할 수 있어요. 예시는 메서드 호출 문서의 블록 지역 변수(block local variables) 설명을 참고해요.

Kernel#local_variables도 함께 알아두면 좋아요. 다만 for 루프는 블록과 달리 새로운 스코프를 만들지 않는다는 점을 기억하세요.

지역 변수와 메서드 (Local Variables and Methods)

Ruby에서 지역 변수 이름과 메서드 이름은 거의 동일해요. 이런 모호한 이름에 할당을 한 적이 없으면 Ruby는 메서드를 호출하려는 것으로 간주해요. 한 번 할당을 하면 지역 변수를 참조하려는 것으로 간주하죠.

지역 변수는 할당이 일어날 때가 아니라 파서가 할당을 만났을 때 생성돼요.

a = 0 if false # does not assign to a

p local_variables # prints [:a]

p a # prints nil

메서드 이름과 지역 변수 이름의 유사성은 혼란스러운 코드를 만들 수 있어요. 예를 들어 둘어볼게요.

def big_calculation
  42 # pretend this takes a long time
end

big_calculation = big_calculation()

이제 big_calculation에 대한 어떤 참조도 지역 변수로 간주되고 캐시돼요. 메서드를 호출하려면 self.big_calculation을 써야 해요.

위처럼 빈 괄호를 쓰거나 self 같은 명시적 수신자(receiver)를 사용해서 메서드 호출을 강제할 수 있어요. 명시적 수신자를 쓸 때 메서드의 가시성이 public이 아니거나 수신자가 리터럴 self이면 NameError가 발생할 수 있어요.

혼란을 자주 일으키는 또 다른 경우는 수식어 if를 쓸 때예요.

p a if a = 0.zero?

"true"를 출력하는 대신 NameError, "undefined local variable or method 'a'"를 받게 돼요. Ruby가 if 왼쪽의 맨몸 그대로의 a를 먼저 파싱할 때는 a에 대한 할당을 아직 보지 못했기 때문에 메서드를 호출하려는 것으로 간주해요. 그다음 Ruby가 a에 대한 할당을 보고 지역 변수를 참조하려는 것으로 간주하죠.

이 혼란은 표현식이 순서대로 실행되지 않는 데서 생겨요. 먼저 지역 변수에 할당된 다음, 존재하지 않는 메서드를 호출하려 하는 거죠.

지역 변수와 eval (Local Variables and eval)

eval로 Ruby 코드를 평가하면 같은 스코프에서 정의된 지역 변수에 접근할 수 있어요. 그 지역 변수들이 eval 호출 이후에야 정의된다 해도 마찬가지예요. 다만 eval 호출 안에서 정의한 지역 변수는 바깥 스코프에 반영되지 않아요. eval 호출 안에서는 바깥 스코프에서 정의한 지역 변수와 호출 안에서 정의한 지역 변수 모두 접근 가능해요. 하지만 같은 스코프에서 이전이나 이후 eval 호출에서 정의한 지역 변수에는 접근할 수 없어요. 각각의 eval 호출을 별개의 중첩 스코프로 생각하면 돼요. 예시를 볼게요.

def m
  eval "bar = 1"
  lvs = eval "baz = 2; ary = [local_variables, foo, baz]; x = 2; ary"
  eval "quux = 3"
  foo = 1
  lvs << local_variables
end

m
# => [[:baz, :ary, :x, :lvs, :foo], nil, 2, [:lvs, :foo]]

인스턴스 변수 (Instance Variables)

인스턴스 변수는 같은 객체의 모든 메서드에서 공유돼요.

인스턴스 변수는 @(앳(At) 표시 또는 commercial at)로 시작해야 해요. 그 외의 규칙은 지역 변수 이름과 같아요. 인스턴스 변수는 @로 시작하기 때문에 두 번째 문자는 대문자가 될 수 있어요.

인스턴스 변수 사용 예시를 볼게요.

class C
  def initialize(value)
    @instance_variable = value
  end

  def value
    @instance_variable
  end
end

object1 = C.new "some value"
object2 = C.new "other value"

p object1.value # prints "some value"
p object2.value # prints "other value"

초기화되지 않은 인스턴스 변수의 값은 nil이에요. 경고를 켠 상태로 Ruby를 실행하면, 초기화되지 않은 인스턴스 변수에 접근할 때 경고를 받을 수 있어요.

value 메서드는 initialize 메서드가 설정한 값에 접근할 수 있는데, 같은 객체에 한해서만 그래요.

클래스 변수 (Class Variables)

클래스 변수는 클래스, 그 서브클래스, 그리고 그 인스턴스들 사이에서 공유돼요.

클래스 변수는 @@(앳 표시 두 개)로 시작해야 해요. 나머지 이름 규칙은 인스턴스 변수와 같아요.

예시를 볼게요.

class A
  @@class_variable = 0

  def value
    @@class_variable
  end

  def update
    @@class_variable = @@class_variable + 1
  end
end

class B < A
  def update
    @@class_variable = @@class_variable + 2
  end
end

a = A.new
b = B.new

puts "A value: #{a.value}"
puts "B value: #{b.value}"

이 코드는 이렇게 출력해요.

A value: 0
B value: 0

같은 예시를 이어서, 두 클래스 중 어느 쪽의 객체로 업데이트해도 값이 공유돼요.

puts "update A"
a.update

puts "A value: #{a.value}"
puts "B value: #{b.value}"

puts "update B"
b.update

puts "A value: #{a.value}"
puts "B value: #{b.value}"

puts "update A"
a.update

puts "A value: #{a.value}"
puts "B value: #{b.value}"

이 코드는 이렇게 출력해요.

update A
A value: 1
B value: 1
update B
A value: 3
B value: 3
update A
A value: 4
B value: 4

초기화되지 않은 클래스 변수에 접근하면 NameError 예외가 발생해요.

클래스는 객체이기 때문에 클래스에도 인스턴스 변수가 있다는 점을 기억하세요. 클래스 변수와 인스턴스 변수를 헷갈리지 않도록 주의해요.

전역 변수 (Global Variables)

전역 변수는 어디에서나 접근할 수 있어요.

전역 변수는 $(달러 기호)로 시작해요. 나머지 이름 규칙은 인스턴스 변수와 같아요.

예시를 볼게요.

$global = 0

class C
  puts "in a class: #{$global}"

  def my_method
    puts "in a method: #{$global}"

    $global = $global + 1
    $other_global = 3
  end
end

C.new.my_method

puts "at top-level, $global: #{$global}, $other_global: #{$other_global}"

이 코드는 이렇게 출력해요.

in a class: 0
in a method: 0
at top-level, $global: 1, $other_global: 3

초기화되지 않은 전역 변수의 값은 nil이에요.

Ruby에는 상황에 따라 다르게 동작하는 특별한 전역 변수들이 있어요. 정규식 매치 변수 같은 것이나 할당될 때 부수 효과가 있는 것들이죠. 자세한 내용은 전역 변수 문서를 참고해요.

할당 메서드 (Assignment Methods)

할당처럼 동작하는 메서드를 정의할 수 있어요. 예를 들어 볼게요.

class C
  def value=(value)
    @value = value
  end
end

c = C.new
c.value = 42

할당 메서드를 사용하면 프로그램이 더 보기 좋아져요. 인스턴스 변수에 할당할 때는 대부분 Module#attr_accessor를 사용해요.

class C
  attr_accessor :value
end

메서드 할당을 사용할 때는 반드시 수신자가 있어야 해요. 수신자가 없으면 Ruby는 지역 변수에 할당하는 것으로 간주해요.

class C
  attr_accessor :value

  def my_method
    value = 42

    puts "local_variables: #{local_variables.join ", "}"
    puts "@value: #{@value.inspect}"
  end
end

C.new.my_method

이 코드는 이렇게 출력해요.

local_variables: value
@value: nil

할당 메서드를 사용하려면 수신자를 지정해야 해요.

class C
  attr_accessor :value

  def my_method
    self.value = 42

    puts "local_variables: #{local_variables.join ", "}"
    puts "@value: #{@value.inspect}"
  end
end

C.new.my_method

이 코드는 이렇게 출력해요.

local_variables:
@value: 42

할당 표현식의 결과값은 항상 할당되는 값이기 때문에, 할당 메서드가 돌려주는 값은 어차피 무시된다는 점을 기억하세요.

축약 할당 (Abbreviated Assignment)

연산자 몇 가지를 할당과 섞어 쓸 수 있어요. 객체에 1을 더하려면 이렇게 쓰면 돼요.

a = 1

a += 2

p a # prints 3

이것은 다음 코드와 같아요.

a = 1

a = a + 2

p a # prints 3

다음 연산자들을 이렇게 사용할 수 있어요: +, -, *, /, %, **, &, |, ^, <<, >>

||=&&=도 있어요. 전자는 값이 nil이거나 false일 때 할당을 하고, 후자는 값이 nil이나 false가 아닐 때 할당을 해요.

예시를 볼게요.

a ||= 0
a &&= 1

p a # prints 1

이 두 연산자는 a = a || 0보다는 a || a = 0처럼 동작한다는 점을 기억하세요.

암묵적 배열 할당 (Implicit Array Assignment)

할당할 때 여러 값을 나열하면 배열을 암묵적으로 만들 수 있어요.

a = 1, 2, 3

p a # prints [1, 2, 3]

이 코드는 Array를 암묵적으로 만들어요.

할당할 때 * 또는 "스플랫(splat)" 연산자를 사용해서 배열을 펼칠 수도 있어요. 이는 다중 할당(multiple assignment)과 비슷해요.

a = *[1, 2, 3]

p a # prints [1, 2, 3]

b = *1

p b # prints [1]

할당의 오른쪽 어디에든 스플랫을 쓸 수 있어요.

a = 1, *[2, 3]

p a # prints [1, 2, 3]

다중 할당 (Multiple Assignment)

오른쪽의 여러 값을 여러 변수에 할당할 수 있어요.

a, b = 1, 2

p a: a, b: b # prints {:a=>1, :b=>2}

다음 섹션들에서 'variable'이 쓰인 자리에는 할당 메서드, 인스턴스, 클래스, 전역도 모두 동작해요.

def value=(value)
  p assigned: value
end

self.value, $global = 1, 2 # prints {:assigned=>1}

p $global # prints 2

다중 할당으로 두 값을 그 자리에서(자리바꿈) 바꿀 수도 있어요.

old_value = 1

new_value, old_value = old_value, 2

p new_value: new_value, old_value: old_value
# prints {:new_value=>1, :old_value=>2}

할당의 오른쪽에 왼쪽 변수보다 값이 더 많으면, 남는 값들은 무시돼요.

a, b = 1, 2, 3

p a: a, b: b # prints {:a=>1, :b=>2}

*를 사용해서 할당의 오른쪽에서 남는 값들을 모을 수 있어요.

a, *b = 1, 2, 3

p a: a, b: b # prints {:a=>1, :b=>[2, 3]}

*는 왼쪽 어디에든 올 수 있어요.

*a, b = 1, 2, 3

p a: a, b: b # prints {:a=>[1, 2], :b=>3}

하지만 하나의 할당에서 *는 하나만 사용할 수 있어요.

배열 분해 (Array Decomposition)

메서드 인자의 배열 분해처럼, 할당 중에도 괄호를 사용해서 배열을 분해할 수 있어요.

(a, b) = [1, 2]

p a: a, b: b # prints {:a=>1, :b=>2}

더 큰 다중 할당의 일부로 배열을 분해할 수도 있어요.

a, (b, c) = 1, [2, 3]

p a: a, b: b, c: c # prints {:a=>1, :b=>2, :c=>3}

각 분해는 그 자체로 하나의 다중 할당으로 간주되기 때문에, 분해 안에서도 *를 사용해서 인자를 모을 수 있어요.

a, (b, *c), *d = 1, [2, 3, 4], 5, 6

p a: a, b: b, c: c, d: d
# prints {:a=>1, :b=>2, :c=>[3, 4], :d=>[5, 6]}

더 알아보기 (Learn more)