Metaprogramming - class variable

Mam taki kawałek kodu, który dla każdej klasy do której includuje MyModule, ma zrobić podklasę MetaClass i wrzucic ją do klasowej zmiennej @@meta:

[code]module MyModule
def self.included(base)
meta_class_name = “#{base}MetaClass”

Object.const_set(meta_class_name, Class.new(MetaClass))

base.class_variable_set(:@@meta, Object.const_get(meta_class_name))

base.class_eval do
  def metaclass
    @@meta                                   # - 1 -
    #self.class.class_variable_get(:@@meta)  # - 2 -
  end
end

end

class MetaClass
end
end

class A
include MyModule
end

class B
include MyModule
end

puts A.class_variable_get(:@@meta)
puts B.class_variable_get(:@@meta)

puts A.new.metaclass
puts B.new.metaclass[/code]
Problem wtedy jest taki, że putsy na końcu wypisują:

[code]AMetaClass
BMetaClass

BMetaClass
BMetaClass[/code]
Czyli same zmienne klasowe są dobrze ustawione, ale w przypadku gdy próbuję dostać się do nich przez metodę metaclass to jakoś dziwnie się nadpisują. Jeżeli zamienię definicje klas A i B miejscami to dwa ostatnie puts wypisują AMetaClass:|
Po paru godzinach walki z tym wiem już, kod z linijki -1- to powodował i jeżeli ją wywalę i odkomentuję -2- to wszystko będzie ok, ale czy mogłby mi ktoś wyjaśnić o co tutaj chodzi?;):wink:

Zmienne @@klasowe są wspólne w hierarchii dziedziczenia.

[code]class A
@@a = 1
end

class B < A
@@a = 2
end

A.class_variable_get(:@@a)
=> 2[/code]

Obczaj sobie przykłady i komentarze w tych plikach, żeby dowiedzieć się jakie masz alternatywy: https://github.com/rails/rails/tree/master/activesupport/lib/active_support/core_ext/class

Hmm, ale u mnie A i B nie są w jednej hierarchii (chyba, że bieżemy pod uwagę też dziedziczenie z Object). Zmienna @@meta, w każdej z nich ma dobrą wartość ustawioną.

puts A.class_variable_get(:@@meta) # => AMetaClass puts B.class_variable_get(:@@meta) # => BMetaClass
Metoda metaclass w wersji -1-, którą dodaję dynamicznie przez base.class_eval, zwraca w przypadku klasy A inną wartość niż ta, która na prawdę zapisana jest w tej klasie w @@meta.
Biorąc pod uwagę to co napisałeś o hierarchii dziedziczenia, czy dzieje się coś takiego, że tak na prawdę próbując pobrać wartość @@meta z Object, a że tam jej nie ma to szuka dalej i znajduje ją dopiero w klasie B?:expressionless:

Ciakawe, Uproszczony case:

[code]module MyModule
def self.included(base)
base.class_variable_set(:@@meta, rand)
base.class_eval do
def metaclass
@@meta # - 1 -
#self.class.class_variable_get(:@@meta) # - 2 -
end
end

end
end

class A
include MyModule
end

class B
include MyModule
end

class C
include MyModule
end

puts C.class_variable_get(:@@meta)
puts B.class_variable_get(:@@meta)
puts A.class_variable_get(:@@meta)

puts A.new.metaclass
puts B.new.metaclass
puts c.new.metaclass[/code]
wcale nie tożsamy case:

[code]module MyModule
def self.included(base)
base.class_variable_set(:@@meta, rand)
end

def metaclass
@@meta # - 1 -
#self.class.class_variable_get(:@@meta) # - 2 -
end
end

class A
include MyModule
end

class B
include MyModule
end

class C
include MyModule
end

puts C.class_variable_get(:@@meta)
puts B.class_variable_get(:@@meta)
puts A.class_variable_get(:@@meta)

puts A.new.metaclass
puts B.new.metaclass
puts c.new.metaclass[/code]

Jak uzyjesz stringa to bedzie sie zachowywac jak oczekujesz

base.class_eval <<-HERE def metaclass @@meta # - 1 - #self.class.class_variable_get(:@@meta) # - 2 - end HERE

Definitywnie nie spodziewałbym się takiego zachowania… Orientujesz się, z czego ono wynika? Przecież w procu dla class_eval użyliśmy def a nie define_method. Dlaczego więc @@meta z def miałby brać coś z jakiegoś innego kontekstu ?

Co ciekawe, w rbx:

rbx-2.0.0pre :002 > module MyModule rbx-2.0.0pre :003?> def self.included(base) rbx-2.0.0pre :004?> base.class_variable_set(:@@meta, rand) rbx-2.0.0pre :005?> base.class_eval do rbx-2.0.0pre :006 > def metaclass rbx-2.0.0pre :007?> @@meta rbx-2.0.0pre :008?> end rbx-2.0.0pre :009?> end rbx-2.0.0pre :010?> end rbx-2.0.0pre :011?> end => #<Rubinius::CompiledMethod included file=(irb)> rbx-2.0.0pre :012 > class A; include MyModule; end => A rbx-2.0.0pre :013 > class B; include MyModule; end => B rbx-2.0.0pre :014 > class C; include MyModule; end => C rbx-2.0.0pre :015 > C.class_variable_get(:@@meta) => 0.6407884506400079 rbx-2.0.0pre :016 > B.class_variable_get(:@@meta) => 0.03229476647600815 rbx-2.0.0pre :017 > A.class_variable_get(:@@meta) => 0.16896995826723749 rbx-2.0.0pre :018 > A.new.metaclass NameError: uninitialized class variable @@meta in module MyModule from A#metaclass at (irb):7 from { } in Object#irb_binding at (irb):18 from Rubinius::BlockEnvironment#call_on_instance at kernel/common/block_environment.rb:72
Czyli próbuje wziąć @@meta widziane z modułu, które nie jest zdefiniowane. Ze stringiem z oczywistych powodów nie ma tego problemu.

Teraz tylko pytanie, dlaczego yarv ma takie domyślne zachowanie? Skąd się bierze to przypisanie pierwszej ustawionej wartości do @@meta?

Ja chyba wszedzie gdzie jest uzyte class_eval widzialem ze przekazywany jest string i wydaje mi sie to bardziej naturalne - wez stringa i evaluj gdziestam w jakims kontekscie.

A taka jeszcze:

[code]module MyModule
def self.included(base)
base.class_eval do
def metaclass
[
@@meta,
self.class.class_eval("@@meta"),
eval(self.class.class_variables[0].to_s),
self.class.class_eval(self.class.class_variables[0].to_s),
self.class.class_variable_get(:@@meta)
]
end
end
end
end

class A
include MyModule
@@meta = “A”
end

class B
include MyModule
@@meta =“B”
end

class C
include MyModule
@@meta =“C”
end
a = A.new
puts a.metaclass
#C
#A
#C
#A
#A[/code]
Jesli uzyje stringa to dostaje same A. Nie mam okazji ostatnio duzo w Ruby robic, a jak juz to i tak nie zaglebiam sie w takie rzeczy, wiec jesli ten post jest w stylu Captain Obvious to sorki :stuck_out_tongue:

Ja bym powiedział, że powodem takiego zachowania jest to, że class_eval z blokiem trzyma kontekst, w rubim też występują domknięcia.

No może i trzyma ale byłbym przekonany, że def w przeciwieństwie define_metod już mnie od tego konktestu odgradza.