Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

어휘 범위 지정 및 Ruby 클래스 변수

Ruby의 클래스 변수는 혼란스럽습니다. 전문 Ruby 사용자도 직관적으로 이해하기 어려울 수 있습니다. 가장 명백한 예는 무자비함과 관련이 있습니다.

class Fruit
  @@kind = nil

  def self.kind
    @@kind
  end
end

class Apple < Fruit
  @@kind = "apple"
end

Apple.kind
# => "apple" 

Fruit.kind
# => "apple" 

kind 변경 자식 클래스의 변수는 부모의 변수도 변경합니다. 꽤 엉망입니다. 그러나 이것이 바로 언어가 작동하는 방식입니다. Smalltalk를 모방하기 위해 오래전에 내린 디자인 결정이었습니다.

심각해집니다

구현상의 단점만큼 아키텍처 선택이 아닌 것처럼 보이는 클래스 변수 이상함의 다른 예가 있습니다. 오늘은 그 중 제가 흥미롭게 생각하는 것에 대해 조금 이야기해 보겠습니다.

이제 두 개의 코드를 비교할 것입니다. 동일한 결과를 생성해야 하는 것처럼 보이지만 그렇지 않습니다.

첫 번째 예에서는 클래스 변수를 설정하고 이를 반환하는 메서드를 만듭니다. 여기에는 멋진 일이 전혀 없습니다. 모든 것이 예상대로 작동합니다.

class Foo
  @@val = 1234

  # This is shorthand for declaring a class method.
  class << self
    def val
      @@val
    end
  end
end

Foo.val
# => 1234

아마도 당신은 이것을 몰랐지만 class << self 클래스 정의 안에 있을 필요는 없습니다. 아래 예에서 우리는 그것을 외부로 옮겼습니다. 클래스 메서드가 추가되었지만 클래스 변수에 액세스할 수 없습니다.

class Bar
  @@val = 1234
end

class << Bar
  def val
    @@val
  end
end

Bar.val

# warning: class variable access from toplevel
# NameError: uninitialized class variable @@val in Object

함수에서 클래스 변수에 액세스하려고 하면 경고와 예외가 발생합니다. 무슨 일이야?

어휘 범위 입력

나는 어휘 범위가 Ruby의 이상하고 혼란스러운 측면의 99%의 근원이라는 것을 점점 더 확신하게 되었습니다.

용어에 익숙하지 않은 경우를 대비하여 어휘 범위 지정은 추상 개체 모델에서 속해 있는 위치가 아니라 코드에서 나타나는 위치를 기준으로 항목을 그룹화하는 것을 의미합니다. 예를 보여주는 것이 훨씬 쉽습니다.

class B
  # x and y share the same lexical scope
  x = 1
  y = 1
end

class B
  # z has a different lexical scope from x and y, even though it's in the same class. 
  z = 3
end

클래스는 사전적으로 결정됨

그렇다면 클래스 변수 예제에서 어휘 범위는 어떻게 작동합니까?

글쎄요, 클래스 변수를 검색하기 위해 Ruby는 그것을 가져올 클래스를 알아야 합니다. 어휘 범위를 사용하여 클래스를 찾습니다.

작업 예제를 더 자세히 살펴보면 클래스 변수에 액세스하는 코드가 클래스 정의에 물리적으로 포함되어 있음을 알 수 있습니다.

class Foo  
  class << self
    def val
      # I'm lexically scoped to Foo!
      @@val
    end
  end
end

작동하지 않는 예제에서 클래스 변수에 액세스하는 코드는 어휘 범위가 클래스로 지정되지 않습니다.

class << Bar
  def val 
    # Foo is nowhere in sight. 
    @@val
  end
end

그러나 클래스에 대해 어휘 범위가 지정되지 않은 경우 범위는 무엇입니까? Ruby가 출력하는 경고는 다음과 같은 단서를 제공합니다. warning: class variable access from toplevel .

작동하지 않는 예제에서 클래스 변수는 어휘적으로 최상위 객체로 범위가 지정됩니다. 이로 인해 정말 이상한 동작이 발생할 수 있습니다.

예를 들어, 어휘 범위가 main으로 지정된 코드에서 클래스 변수를 설정하려고 하면 클래스 변수가 Object에 설정됩니다. .

class Bar
end

class << Bar
  def val=(n)
    # This code is lexically scoped to the top level object. 
    # That means, that `@@val` will be set on `Object`, not `Bar`
    @@val = i
  end
end

Bar.val = 100

# Whaa?
Object.class_variables
# => [:@@foo]

더 많은 예

클래스의 어휘 범위 외부에서 클래스 변수를 참조하는 방법에는 여러 가지가 있습니다. 그들 모두는 당신에게 문제를 줄 것입니다.

다음은 즐거움을 위한 몇 가지 예입니다.

class Foo
  @@foo = :foo
end

# This won't work
Foo.class_eval { puts @@foo }

# neither will this
Foo.send :define_method, :x do 
  puts @@foo
end

# ..and you weren't thinking about using modules, were you? 
module Printable
  def foo
    puts @@foo
  end
end

class Foo
  @@foo = :foo
  include Printable
end

Foo.new.foo

이제 왜 모든 사람들이 Ruby에서 클래스 변수를 사용하지 말라고 하는지 알 것입니다. :)