Porozmawiajmy z Rubim: irb i proste wyrażenia
(5 komentarzy)W kategoriach: Ruby , Ruby tutorial , Techblog / 07 kwietnia 2007 [00:58:26]
Tagi technorati: irb programming ruby tutorial
W Sieci można znaleźć wiele tutoriali uczących od podstaw języka Ruby, w których irb pojawia się już w pierwszej części. My natomiast znamy już podstawy i dopiero teraz przejdziemy do poznawania tego narzędzia - dzięki temu używać będziemy go w sposób bardziej dojrzały i zrozumiały.
Zaczynamy.
Po poprawnym zainstalowaniu Rubiego, wpisanie w wierszu poleceń irb powinno uruchomić nam interaktywną powłokę Rubiego, którą będziemy nazywać po imieniu: irb.
C:\Documents and Settings\RazorJack>irb
irb(main):001:0>
Witamy w irb. Zobaczmy, co potrafi.
irb(main):001:0> 4
=> 4
irb(main):002:0> 2 + 3
=> 5
irb(main):003:0> a = 2**8
=> 256
irb(main):004:0> a
=> 256
irb(main):005:0> puts a
256
=> nil
Można powiedzieć, że irb posiada wyjście dwukanałowe. Pierwszy z nich jest wyjściem standardowym (na które przekazujemy dane za pomocą np. puts), drugi natomiast umożliwia nam podgląd wartości zwracanej przez wyrażenie. Jak oba rozróżnić? Bardzo prosto - to drugie odznacza się wierszem rozpoczętym przez =>.
Nim kontynuujemy naszą sesję z irb-em, należy zaznaczyć jedną ważną kwestię. Duża większość konstrukcji w Rubim to wyrażenia. Fakt, że nie "zbieramy" wyniku do jakiejkolwiek zmiennej wcale nie oznacza, że to, co właśnie wykonał Ruby nie jest wyrażeniem. Na chwilę obecną po prostu to zapamiętajmy. ale w przyszłości niech to będzie dla nas jeden z wielu atutów Rubiego, dzięki którym nasz kod będzie jeszcze bardziej zwięzły i czytelny.
Zajrzyjmy nieco głębiej w Rubiego, a konkretniej: w klasy.
irb(main):006:0> a.class
=> Fixnum
irb(main):007:0> b = Array.new
=> []
irb(main):008:0> b = [9, 2.7, "tekst", [2, 3 ,4]]
=> [9, 2.7, "tekst", [2, 3, 4]]
irb(main):009:0> b.each { |element| puts element.class }
Fixnum
Float
String
Array
=> [9, 2.7, "tekst", [2, 3, 4]]
Ładnie. Jesteśmy już w stanie wyśledzić klasę poszczególnych zmiennych i wyrażeń. Nic nie stoi na przeszkodzie, by sprawdzić też inne obiekty (jakiej klasy jest nil?).
Znamy klasy - ale to nie wszystko. Musimy wiedzieć co możemy z nimi zrobić.
Podpowiadanie nazw metod
Czasem zdarza nam się zapomnieć (lub nie znać) nazwy jakiejś metody. Z pomocą przychodzi nam rozszerzająca bibliotekacompletion dla irb-a:
irb(main):011:0> require 'irb/completion'
=> true
irb(main):012:0> b.r
b.rassoc b.replace b.respond_to? b.reverse_each
b.reject b.require b.reverse b.rindex
b.reject! b.require_gem b.reverse!
Podpowiadanie wywołujemy naciśnięciem klawisza Tab.
Analizujemy głębiej klasy
Biblioteka completion potrafi zasypać nas dość niemiłą, bo niezwykle długą, listą metod. Na szczęście Ruby umożliwia nam uzyskiwanie informacji o bardziej sprecyzowanych kryteriach na temat nie tylko metod, ale i innych elementów obiektu (zmiennych klasy, metod chronionych, metod singletonów i wiele innych).
Zagadnienie to jest niezwykle rozbudowane i nie czas i miejsce na rozpisywanie się na ten temat. Zachęcam do samodzielnego poznania tych wszystkich możliwości Rubiego, a pomocą niech się okaże:
irb(main):013:0> a.public_methods.sort.each { |m| puts m }
... oraz...
ri
Ruby jest standardowo wyposażony w niezwykle pomocną dokumentację języka. Z przyczyn oczywistych nie będziemy tutaj wklejać obszernych wyników działania aplikacji, skupimy się raczej na samym sposobie używania tego narzędzia.
Użyjmy więc wiersza poleceń, by dowiedzieć się czegoś na temat tablic, metody reverse klasy Array, a także metody is_a?, którą posiada każdy obiekt:
ri Array
ri Array#reverse
ri is_a?
Definiujemy metodę
irb(main):001:0> def hello(name)
irb(main):002:1> puts "Witaj #{name}!"
irb(main):003:1> end
=> nil
Od tego momentu metoda ta istnieje w aktualnie wywołanym środowisku irb. Zaraz, zaraz. Nie ma wolnych metod, każda przecież musi należeć do jakiejś klasy. W Rubim jest podobnie - napisana przez nas metoda została dodana do modułu Kernel, który automatycznie jest dołączany do każdej klasy na zasadzie tzw. miksinów (o których dowiemy się więcej za jakiś czas). Dzięki automatycznemu odwoływaniu się do "globalnego obiektu" jesteśmy w stanie korzystać z takich metod jak puts na sposób znany nam ze starego, dobrego C.
irb(main):020:0> hello("Ruby")
Witaj Ruby!
=> nil
irb(main):021:0> hello "Ruby"
Witaj Ruby!
=> nil
Widzimy tutaj równoważność tych dwóch wywołań - wybór pozostaje więc w rękach naszych preferencji. Ponieważ, jako programiści Rubiego, cenimy sobie prostotę i estetykę, wszyscy zgodnie wybieramy wariant drugi :). Niestety, przy zagnieżdżonych i skomplikowanych wywołaniach zmuszeni będziemy użyć nawiasów, by uniknąć niejednoznaczności wyrażeń.
Wyrażenia w Rubim
Otwórzmy nasz ulubiony edytor kodu i napiszmy metodę:
- o zmiennej ilości parametrów
- z pierwszym argumentem o domyślnej wartości
- pozostałe parametry niech będą opcjonalne
- metoda ma witać wszystkie podane jej imiona, wymieniając je ładnie po przecinku - a powitanie ma zakończyć wykrzyknikiem
Postarajmy się o zwięzłość kodu i wykorzystanie jak największej liczby atutów Rubiego. Oto, co nam może powstać:
def hello(name = "Ruby", *others)
puts "Hello " + name + (", " if others.any?).to_s + others.join(", ") + "!"
end
Trzeba przyznać, że zwięzłość składni Rubiego bywa porażająca.
Należy się kilka słów wyjaśnień
- zmienną liczbę argumentów deklarujemy, poprzedzając nazwę argumentu gwiazdką; argument ten jest normalną tablicą, do której odwołujemy się poprzez całkowite indeksy oraz iteratory
- wyrażenie
(", " if others.any?)oznacza mniej więcej tyle, co: "Jeśli podano jakikolwiek opcjonalny argument, zwróć ciąg ', ', oddzielający argument name od pierwszego argumentu opcjonalnego" to_sw powyższym wyrażeniu jest niezbędne; rozważmy dwie sytuacje: jeśli podany został opcjonalny argument, wyrażenie zwraca "," - w przeciwnym wypadku przyjmie wartośćnil, który, ze względu na różnicę typów, nie może zostać załączony do jakiegokolwiek łańcucha; musi więc ulec konwersji (za pomocą metodyto_s) na String - pustyjoinjest odpowiednikiem funkcjiimplodeznanej z PHP - łączy wszystkie elementy tablicy w jeden String, stosując swój parametr jako separator
Kiedy już wszystko jest jasne, wypróbujmy naszą nową metodę (którą zapisaliśmy do pliku test.rb) w irb-ie:
irb(main):001:0> load 'test.rb'
=> true
irb(main):002:0> hello
Hello Ruby!
=> nil
irb(main):003:0> hello "Rails"
Hello Rails!
=> nil
irb(main):004:0> hello "Ruby", "Rails", "and world"
Hello Ruby, Rails, and world!
=> nil
Jak widać, wszystko działa jak należy.
Teraz, kiedy potrafimy posługiwać się interaktywną powłoką Rubiego, mamy już wystarczające podstawy, by móc analizować działanie wyrażeń w tym języku. Co uczynimy - w następnym odcinku.
s
07 kwietnia 2007, 13:02:53W R. nie podoba mi się użycie plusa do konkatenacji która z dodawaniem nie ma nic wspólnego. Btw, coby wypisać listę oddzieloną przecinkami nie muszę zlepiać napisów (działa tak jak przykład z tego odcinka tuta):
(defun hello (&optional (name "Ruby") &rest others) (format t "Hello ~{~A~^, ~}!" (cons name others)))Czy w R. jest do tego (wypisywania sekwencji) jakiaś specjalna konstukcja, idiom, łotever ? Formata ktoś kiedyś dla R. napisał, ale ostatnia wersja jest z 2003 roku więc nie wiem czy działa.
s
07 kwietnia 2007, 13:04:22coś jest nie tak z formatowaniem, ten odstęp po sekcji pre to nie moja wina.
DOPISKA: można sobie odpuścić to_s jeśli jawnie zwrócimy pusty napis:
def hello(name = "Ruby", *others) puts "Hello " + name + (others.any? ? ", " : "") + others.join(", ") + "!" endtrochę głupio dwa pytajniki wyglądają ale takie coś wydaje mi się bardziej
idiomatyczne (generalnie rzecz biorąc bo nie znam rubicznych zwyczajów).
Btw, ogólnie imho to niezbyt eleganckie jest, przykład nie wysyła na wyjście naprzemiennie kolejnych składowych sekwencji identyfikowanej przez
othersi łańcucha", "tylko pierwiej wszystko lepi do kupy a dopiero potem zapodaje na stdout.RazorJack
08 kwietnia 2007, 01:35:20Spytaj kogoś z ulicy, ile to jest 2 + 2; odpowie, że 4. Spytaj kogoś z ulicy ile to jest „abra” + „kadabra”. Odpowie, że „abrakadabra”. Spytaj kogoś z ulicy ile to jest „ha”*3. Bytrzejsi odpowiedzą, że „hahaha”. Czemu z ulicy? Bo oni wiedzą najlepiej, czym jest dla człowieka znak „plus”, oni skojarzą to intuicją, a nie wiedzą programistyczną. A czytanie kodu za pomocą intuicji jest najszybsze i najbardziej bezbłędne.
Owszem, konkatenacja to nie jest to samo, co dodawanie. Ale znak „plus” jest w naszym umyśle tak silnie zakodowany jako dodawanie czegokolwiek, że jego użycie jako operatora do sklejania Stringów jest wręcz zalecane. Bo w Rubim dbamy o czytelność i przyjazność kodu – nie o zadowolenie purystów językowych i semantycznych.
Przyznam szczerze, że nie wiem, czy istnieje coś podobnego w Rubim. Ale prawdę mówiąc, wyrażenia w Rubim dają wystarczającą zwięzłość, a czytelnością biją na głowę LISPową (dobrze mówię?) konstrukcję, którą wyżej zaprezentowałeś. Ja takich sekwencji nie potrzebuję – czy inni zwolennicy Rubiego podzielają moje zdanie?
Fajnie wykombinowałeś, ten sposób też mi się podoba :).
Co do nieelegancji – nie rozumiem co takiego jest w tym nieeleganckiego. :)
s
08 kwietnia 2007, 03:27:46Jeśli chodzi konkatenację to z praktycznego punktu widzenia to czy to będzie plus czy co innego nie ma większego znaczenia. Chyba można się przyzwyczaić, niemniej jak już pisałem nie podoba mi się ten pomysł, taki mam gust. Spytałem przed chwilą osoby nieskomputeryzowane (ale nie na ulicy bo przeziębiony jestem), na pytanie ile to jest
"jabłko"plus"jabłko"otrzymałem odpowiedź"dwa jabłka", natomiast na"jabłko"plus"gruszka"—"jabłko i gruszka". Zadałem pytanie pomocnicze czy przez"jabłko i gruszka"rozumie"jabłkogruszka". Okazało się że nie, po prostu na wyjściu otrzymałem to co zapodałem na wejściu:"jabłko","gruszka". Na pytanie ile to jest"ha"*3otrzymałem odpowiedź"ha" "ha" "ha"(upewniłem się że nie miała na myśli"hahaha", tzn. dostałem 3 krótkie słowa a nie jedno długie). Z tego wynika że dla tej osoby intuicyjnym wynikiem na komputerze (tak sobie dopowiadam) było by["ha", "ha", "ha"]a nie"hahaha".Póki co spytałem dwie osoby bo późno jest… trochę nie chce mi się kontynuować ale jeszcze popytam.
Mi osobiście próba dodania napisów kojarzy się z automagiczną konwersją tychże do liczb i zsumowanie :( jeśli już... jak wcześniej pisałem takie przeciążenie plusa mi się nie podoba.
Afaik nie ma czegoś takiego jak „wrodzona” intuicja w czytaniu kodu. Osoby bez imprintingu przyswajały rzeczy nietypowe i odrzucanie przez zawodowców (którzy marudzili na nieintuicyjność) równie łatwo (lub trudno) jak te mainstreamowe :> Robiono takie badania. Z praktycznego punktu widzenia ma jakiegoś globalnego maksimum jeśli chodzi o czytelność, liczą się wcześniejsze doświadczenia.
Te brzydactwa którymi zaśmiecam Ci tutorial nawet koło Smalltalka nie stały. Smalltak jest językiem estetycznym. Btw, ja te nieczytelne rzeczy czytam szybciej niż np. Twój przykład. Tzn. one mi się same czytają ^^ to kwestia tego ile czasu się z danym językiem spędziło.
Sekwencji (w Rubym są chyba tylko wektory) potrzebujesz na pewno ^^ mi chodziło o zaawansowaną funkcję drukującą (jakieś printf na sterydach). Czy to komukolwiek, rubystów włączając, potrzebne ? Nie wiem. Niemniej zadano sobie trud zaimplementowania operatora
FORMATw jego perwersyjnej wersji z iteracją (dlatego można tym łatwo drukować sekwencje i drzewa), skokiem, warunkami, etc (FORMATto taki język w języku). Czy ktoś (z rubystów) tego używa ? pojęcia nie mam.FORMAT/ tzw. „Hollerith strings” są afaik uważane za zuo bo są mało czytelne. Ja je tam lubię, sentymentalny jestem.Być może użyłem niewłaściwego słowa, ale inne jakoś mi do głowy nie przychodzi. Nie chodziło mi o czytelność tylko o zasoby. Kiedyś się zetknąłem z takim czymś w kodzie generującym duże pliki (zrzut różnych struktur danych, oczywiście w plikach trzeba było jakiś separatorów: spacji, przecinków, etc.). Oczywiście to był prototyp, ktoś się spieszył... tak ogólnie to nieszkodliwe, ale nie lubię takiego marnotrawstwa (jeśli oczywiście usunięcie go nie zajmuje dużo czasu, nie ma sensu optymalizować kodu zanim go nie sprofilujemy, tyle że przykład o którym mowa jest, że tak powiem, klasyczny (jeśli gc jest głupi to takie coś go przytka)).
Ja się czepiam dla sportu bo mnie grypa dopadła i się nudzę :)
RazorJack
08 kwietnia 2007, 10:47:41Od końca :).
Zasobożerność – całkowicie się z Tobą zgadam. Przy ogromnych danych taka realizacja bierze w łeb. Ale w Rubim można przecież robić na inne sposoby. Nie utrudniajmy sobie tylko życia, jeśli problem jest prosty.
Zaawansowane drukowanie – mam wyrażenia. Są czytelne i zwięzłe. Uważam, że nie potrzebuję więcej. Być może za mało jeszcze kodu napisałem w Rubim – ale póki co tak uważam.
Smalltalk.
Oczywiście chodziło mi o LISP-a :). Pisząc komentarz miałem pootwierane coś o Smalltalku i podświadomie mi się on wdarł do komentarza. Poprawiłem od razu – ale Ty byłeś szybszy :).
Intuicja.
Przyznasz, ze zarówno niskopoziomowe C, jak i wysokopoziomowy Ruby mają pewne cechy wspólne – sam mechanizm programowania. Kiedy mamy go opanowanego, pewne nieznane wyrażenia stają się dla nas czytelne – bo wiemy w czym rzecz. Im takie wyrażenie jest bardziej zbliżone do naszej mowy i naszej logiki – tym bardziej zrozumiały jest kod. Mówiąc obrazowo: mózg to rozumie „natywnie”, nie musi wykonywać wielu „zapytań”, by rozszyfrować konstrukcję :).
Ludzie z ulicy.
Trochę zbyt dosłownie potraktowałeś ten przykład. Ale: zapytaj ich ile to jest
"abra" + "kadabra":). A co do"ha" "ha" "ha"– wpisz to wyrażenie w irb – wszystko się zgadza :).