Porozmawiajmy z Rubim: irb i proste wyrażenia

Posted by Jacek Galanciak on

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.

$ 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_s w 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ą metody to_s) na String - pusty
  • join jest odpowiednikiem funkcji implode znanej 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.