cv.cm

編碼 · 程式語言

Ruby 入門教學

閱讀約 7 分鐘

簡答

Ruby 是動態的物件導向腳本語言,設計目標是讓人讀寫起來都愉快。將 puts "Hello, world" 存成 hello.rb,再執行 ruby hello.rb。在 Ruby 中一切皆物件,連數字和 nil 也不例外。核心概念包括以 def 定義的方法、陣列與雜湊、傳給 each 和 map 等迭代器的區塊、類別與模組,以及以 begin/rescue 處理的例外。

開啟正規測試

Ruby 是什麼,用來做什麼

Ruby 由日本的松本行弘(Yukihiro Matsumoto,人稱「Matz」)創造,於 1995 年首次發布,目標是讓程式設計師感到快樂。它因 Ruby on Rails 而廣為人知,GitHub 和 Shopify 等網站就是使用這個網頁框架。Ruby 用於網頁應用程式、腳本與自動化,以及 Homebrew 和 CocoaPods 等開發者工具。它的標準實作是 CRuby(也稱為 MRI)。

安裝 Ruby 並執行 Hello, world

macOS 和許多 Linux 系統都內建 Ruby,但通常版本很舊;請用 rbenv、asdf 或 mise 等版本管理工具安裝目前的 Ruby 3,Windows 上則可使用 RubyInstaller。用 ruby -v 確認。

puts "Hello, world"
ruby hello.rb
# Hello, world
irb          # interactive Ruby shell

puts 會印出值並加上換行。當語意明確時,方法引數的括號可以省略。

變數與基本型別

變數不需要宣告,可以存放任何物件。依慣例,名稱使用 snake_case。常數以大寫字母開頭。

類別範例說明
Integer42, 2**100大小不限
Float3.14雙精度
String"hi #{name}", 'hi'可變;雙引號會內插變數
Symbol:name不可變的識別字,常用作雜湊的鍵
true, falsetrue只有 false 和 nil 為假值
NilClassnil沒有值
Array, Hash[1, 2], {a: 1}內建集合
name = "Ada"
count = 7
puts "#{name} has #{count} items"   # Ada has 7 items
puts count / 2, count / 2.0         # 3, then 3.5
puts "12".to_i + 1                  # 13
puts 5.class, nil.to_a.inspect      # Integer, then []
puts 0 ? "0 is truthy" : "falsy"    # 0 is truthy

流程控制

score = 72
if score >= 90
  puts "A"
elsif score >= 70
  puts "B"          # B
else
  puts "C"
end

puts "pass" unless score < 50    # statement modifier

3.times { |i| print i, " " }     # 0 1 2
puts

n = 10
n /= 2 while n > 1
puts n                           # 1

kind = case "SAT"
       when "SAT", "SUN" then "weekend"
       else "weekday"
       end
puts kind                        # weekend

區塊以 end 結尾,而不是大括號。Ruby 程式碼通常以 2 個空格縮排。

方法

方法會回傳最後一個運算式的值,因此通常不需要寫 return。依慣例,回傳布林值的方法以 ? 結尾,會就地修改接收者的方法則以 ! 結尾。

def greet(name, greeting: "Hello")
  "#{greeting}, #{name}!"
end

def total(*nums)
  nums.sum
end

puts greet("Lin")                   # Hello, Lin!
puts greet("Lin", greeting: "Hi")   # Hi, Lin!
puts total(1, 2, 3)                 # 6
puts 4.even?, "abc".upcase          # true, then ABC

s = "hello"
s.upcase!          # modifies s in place
puts s             # HELLO

陣列與雜湊

nums = [3, 1, 2]
nums << 5
p nums.sort, nums.first, nums.last   # [1, 2, 3, 5] then 3 then 5
p nums.sort[1..2]                    # [2, 3]

ages = { "ann" => 31, "bob" => 25 }
ages["cy"] = 40
ages.each { |person, age| puts "#{person} is #{age}" }
p ages.fetch("zed", 0)               # 0

user = { name: "Ann", city: "Oslo" } # symbol keys
p user[:name]                        # "Ann"

p 會印出值的 inspect 形式,因為會顯示引號與括號,所以很適合除錯。

招牌特色:區塊與迭代器

區塊是傳給方法的一段程式碼,單行時寫成 { ... },多行時寫成 do ... end。Ruby 的迭代主要透過區塊而非 for 迴圈完成,而 Enumerable 的方法讓資料轉換寫起來很簡短。

nums = [1, 2, 3, 4, 5]
p nums.map { |n| n * n }               # [1, 4, 9, 16, 25]
p nums.select(&:even?)                 # [2, 4]
p nums.reduce(0) { |sum, n| sum + n }  # 15
p nums.each_slice(2).to_a              # [[1, 2], [3, 4], [5]]
p %w[pear fig apple].sort_by(&:length) # ["fig", "pear", "apple"]

def twice
  yield
  yield
end
twice { puts "hi" }   # prints hi two times

&:even? 是 { |n| n.even? } 的簡寫。你自己寫的方法可以接收區塊,並用 yield 呼叫它。

類別與模組

module Greeter
  def hello
    "Hello from #{name}"
  end
end

class Account
  include Greeter          # mix in module methods
  attr_reader :name, :balance

  def initialize(name)
    @name = name           # @ marks an instance variable
    @balance = 0
  end

  def deposit(amount)
    raise ArgumentError, "amount must be positive" unless amount.positive?
    @balance += amount
    self
  end

  def to_s = "#{name}: #{balance}"
end

acc = Account.new("Ann")
acc.deposit(30).deposit(20)
puts acc        # Ann: 50
puts acc.hello  # Hello from Ann

attr_reader 會產生 getter 方法。模組把方法組合在一起,可以混入(mix in)許多類別,Ruby 用這種方式取代多重繼承。單行的 def to_s = ... 寫法需要 Ruby 3.0 以上。

錯誤處理

def parse_age(text)
  age = Integer(text)       # raises ArgumentError on bad input
  raise ArgumentError, "negative age" if age.negative?
  age
end

begin
  puts parse_age("41")      # 41
  puts parse_age("abc")
rescue ArgumentError => e
  puts "error: #{e.message}"  # error: invalid value for Integer(): "abc"
ensure
  puts "done"
end

rescue 捕捉例外,ensure 則一定會執行。在方法內部可以直接寫 rescue,不需要 begin。

完整的小程式:單字頻率統計

path = ARGV.fetch(0) { abort "usage: ruby words.rb FILE" }

begin
  text = File.read(path).downcase
rescue Errno::ENOENT
  abort "file not found: #{path}"
end

counts = text.scan(/[a-z']+/).tally
counts.max_by(5) { |_word, n| n }.each do |word, n|
  puts format("%5d  %s", n, word)
end

以 ruby words.rb notes.txt 執行。tally 會將相同的項目計數並存入雜湊,max_by(5) 則回傳計數最高的五個項目。

初學者常見錯誤

  • 忘了用 end 結束 if、def、class 或 do 區塊。
  • 混用字串鍵與 symbol 鍵:h["name"] 和 h[:name] 是不同的鍵。
  • 使用單引號卻期待 #{} 內插能運作。
  • 呼叫了 ! 方法,卻沒發現它改變了原本的物件。
  • 使用系統內建的舊版 Ruby,而不是用版本管理工具安裝的目前版本。

接下來學什麼

從 ruby-lang.org 的文件頁面開始,包括快速入門「Ruby in Twenty Minutes」,並使用 docs.ruby-lang.org 查閱核心與標準函式庫參考。學習管理相依套件的 Bundler 與 gem、用於測試的 RSpec 或 Minitest,之後如果想開發網頁應用程式,再學 Ruby on Rails。

常見問題

現在還有人用 Ruby 嗎?

有。Ruby on Rails 驅動著許多成熟的網頁產品,包括 GitHub 和 Shopify,Ruby 也常用於腳本與開發者工具。新專案採用它的情況不如 Python 或 JavaScript 常見,但它仍持續積極開發中。

Ruby 和 Ruby on Rails 有什麼差別?

Ruby 是程式語言。Rails 則是以 Ruby 撰寫的網頁框架,提供路由、資料庫存取與頁面範本。

Ruby 中的 symbol 是什麼?

像 :name 這樣的 symbol 是不可變且唯一的識別字。相同的 symbol 永遠是同一個物件,因此很適合用作雜湊的鍵與選項名稱。

初學者該選 Ruby 還是 Python?

兩者都是語法易讀的好入門語言。Python 的應用範圍更廣,特別是在資料科學領域;Ruby 的表達力很強,搭配 Rails 開發網頁也很出色。

全部編碼教學