Modyfikacja parametru metody

W Ruby mamy wiele metod z wykrzyknikiem czyli takich, które modyfikują argumenty tych metod. Jak coś takiego uzyskać? Przykład kodu, z którym walczę:

[code]sections.each do |section|
parse_paragraph(section) unless parse_header(section)
end

def parse_header(text)
if text =~ /^!/
text = “

text


else
nil
end
end

def parse_paragraph(text)
(…)[/code]
Oczywiście kod jest bardziej skomplikowany ale chodzi o zasadę. Chciałbym aby efektem były odpowiednio “przerobione” elementy tablicy sections

W takim wypadku nie lepiej byłoby zrobić sections.map! ?

sections.map! do |section| parse_header(section) || parse_paragraph(section) end

Super. Przeglądając dokumentację klasy Array nie zauważyłem map!. To mi sporo ułatwia, dzięki.

http://radarek.jogger.pl/2009/02/21/ruby-a-metody-z-i-w-nazwie/ Radarek kiedyś przeprowadził ładne śledztwo, o co chodzi z wykrzyknikami i znakami zapytania.

Dzięki za tego linka, bardzo ciekawy materiał. Ja jednak nadal nie wiem jak napisać metodę, która modyfikuje parametr wywołania.

Lektura tego wątku jest bardzo oświecająca w kwestii przekazywania argumentów celem ich zmiany w wywołującym zakresie
http://groups.google.com/group/ruby-talk-google/browse_thread/thread/31ad84bf4f2bc10b?pli=1

Faktycznie, oświecająca. Z tekstu wynika, że String nie jest typem “immediate” więc do funkcji przekazywana jest referencja. Zatem

irb(main):019:0> t = 'text' => "text" irb(main):020:0> def fn1(str) irb(main):021:1> str.gsub!(/x/,'r') irb(main):022:1> end => nil irb(main):023:0> fn1 t => "tert" irb(main):024:0> t => "tert"
Wygląda na to, że działa. Ale …

irb(main):025:0> def fn2(str) irb(main):026:1> str = "<h1>#{str}</h1>" irb(main):027:1> end => nil irb(main):028:0> fn2 t => "<h1>tert</h1>" irb(main):029:0> t => "tert"
Jednak nie do końca. Skoro String przekazywany jest przez referencję to “=” powinno obiektowi wskazywanemu przez str przekazać zawartość tego co z prawej strony albo, przyjmując, że “=” to funkcja, przypisać zmiennej str adres na jaki wskazuje String będący parametrem. Niestety nic takiego się nie dzieje.

Nadal tego nie ogarniam.

Oświecająca to będzie jak ją przeczytasz. Masz przecież:

[quote]I think “pass by reference” is not the proper term because that would
imply that you could change a variable in the calling scope, i.e. you
could do

def magic(x) x = 10 end
foo = 1
puts foo # prints 1
magic(foo)
puts foo # prints 10

which you can’t. I’d rather call it “call by reference value”, i.e.
the reference is copied.[/quote]
A żeby Ci to trochę jeszcze jaśniej przekazać: przypisanie

str = "<h1>#{str}</h1>"

tworzy nową zmienną str i ustawia jej wartość jako nowy string, do którego kopiowana jest zawartość wcześniej dostępna pod str. Niemniej są to dwa różne obiekty. Przyjmij w tym miejscu że stringi są w rubym immutable (niezmienne), zmiana zawartości przez przypisanie jest po prostu stworzeniem nowej zmiennej o nowej, innej zawartości, a starą zawartością z racji braku referencji zajmie się śmieciarz przy najbliższej okazji.

Prosty trik – object_id podaje Ci wewnętrzny numer obiektu w interpreterze, co pozwala stwierdzić czy wciąż mowa o tym samym obiekcie czy też jego nowej kopii:

ruby-1.9.2-head > str = "dupa" => "dupa" ruby-1.9.2-head > str.object_id => 76798210 ruby-1.9.2-head > str = "<h1>#{str}</h1>" => "<h1>dupa</h1>" ruby-1.9.2-head > str.object_id => 76792740
Metody które chciałbyś zreplikować, czyli String#gsub!, nie zmieniają object_id i faktycznie działają in-place,

ruby-1.9.2-head > str.object_id => 76792740 ruby-1.9.2-head > str.gsub!("a","AA") => "<h1>dupAA</h1>" ruby-1.9.2-head > str.object_id => 76792740
ale odbywa się toto, jeśli dobrze pamiętam (dawno nie czytałem źródeł MRI), za pomocą małego triku, to znaczy dostępu nie tyle do stringa, co wejście głębiej w jego elementy składowe i potraktowanie go jak tablicę:

ruby-1.9.2-head > str[1] => "h" ruby-1.9.2-head > str[1] = "z" => "z" ruby-1.9.2-head > str => "<z1>dupAA</h1>upa" ruby-1.9.2-head > str.object_id => 76728140
W skrócie: zewnętrzną metodą nie zmienisz obiektu (interpreter stworzy kopię przy pierwszym przypisaniu), ale możesz mu pomieszać w elementach składowych. Jeśli już musisz to robić (to naprawdę jest kiepski pomysł, zapytaj kolegów od programowania funkcyjnego), to przynajmniej nazywaj takie niebezpieczne metody z wykrzyknikami

ruby-1.9.2-head > str = "dupa" => "dupa" ruby-1.9.2-head > def upupiaj!(s) ruby-1.9.2-head ?> s[0] = "p" ruby-1.9.2-head ?> end => nil ruby-1.9.2-head > str => "dupa" ruby-1.9.2-head > upupiaj!(str) => "p" ruby-1.9.2-head > str => "pupa"

http://javadude.com/articles/passbyvalue.htm - Dosyć ciekawy artykuł. O javie co prawda ale w Rubym jest bardzo podobnie
(nie wiem czy w implementacji, ale rezultat jest taki). Tak w ramach ciekawostki bo w sumie Tomash wyczerpał temat.

Jak dla mnie wszystko jest prost jak człowiek przyjmie, że cokolwiek podajesz dalej to jest to wskaźnik na obiekt a metody wywołujesz na obiekcie, na który on wskazuje.
Jeśli więc w wywołanej metodzie pod zmienną podstawiasz inny obiekt to mówisz “ej ty, zmienno! wskazuj mi na ten obiekt tutaj” ale to nie zmienia faktu, że w zewnętrznym kontekście zmienna wciaż wskazuje na poprzedni obiekt. Proste jak drut.

Albo inaczej: To że w zewnętrznym kontekście (który woła jakąś metodę) i wewnętrznym (wewnątrz metody) zmienna nazywa się tak samo to nie znaczy, że to jest ta sama zmienna. Ona tylko ma tą samą nazwę dla twojej wygody bo tak sobie napisałeś ale to są 2 różne zmienne.

Aleś teraz zagmatwał…

Według mnie prościej wytłumaczyć niż zrobił to paneq się nie da i więcej bym tu nie dodawał.

Powiedzmy sobie szczerze - to są absolutne podstawy programowania. Wielkie brawa dla Tomasza, że miał tyle cierpliwości, żeby ten problem tak szeroko i łagodnie opisać :]

Wielkie dzięki Tomash za te wyjaśnienia. W sumie to sam do tego doszedłem ale już w łóżku :). Przypomniałem sobie lekturę p.Bieleckiego dotyczącą C i wykorzystania wskaźników. Poskładałem wszystko razem i doszedłem do tego co napisałeś.
Dzięki za cierpliwość.

Spoko, sam miałem kilka mindfucków podczas zabawy z Rubym dotyczących właśnie działania niektórych funkcji i operatorów :slight_smile: