Ruby 入門

Ruby 入門

原本是在練習 Kubernetes 的時候,由於需要模擬節點,所以會使用到 Virtualbox 來建立虛擬機,而使用的軟體是 Vagrant。

為了要更能理解 Vagrant 的設定寫法,所以才有了這一篇學習 Ruby 的文章。

Ruby 是一種物件導向、指令式、函數式、動態的通用程式語言。在20世紀90年代中期由日本電腦科學家松本行弘(Matz)設計並開發。

本筆記參考以下課程:はじめてのRuby on Rails入門-RubyとRailsを基礎から学びウェブアプリケーションをネットに公開しよう

初期先以 Syntax 為主要學習目標。

Installation

可以看到有一個 Devkit 的東西在,這個為了讓 Ruby 可以去使用以 C 寫的第三方 Library 用的,如果您要引入的 library 是使用純 Ruby 寫的,那麼就不需要安裝 devkit。

Ruby 跟 Java 一樣都有一個 VM,稱為 RVM,儘管實現方式有所不同。

Document

Ruby 入門

要執行 Ruby 程式有兩種方法:

  1. 使用 irb (Interactive Ruby),類似互動式的方法
    • 直接在 CMD 輸入 irb 就可以進入環境。
  2. 執行以檔案形式保存的程式

在 Ruby 中, nil 是一個物件,表示『沒有值』或『未定義的值』,可以用來代表一個變數未被賦值或一個方法沒有返回值。

在 IRB 中,有以下快捷鍵可以使用:

快捷鍵 作用 說明
Ctrl + L 畫面清除
Ctrl + D 離開 IRB 輸入 exit 也有一樣的效果

Hello world

直接建立一個檔案:sample.rb

# puts  是 Ruby 中的一個方法,用於將字符串或其他數據輸出到控制台。
# 它的作用類似於  print  方法,但是在輸出完數據之後會自動換行。
puts "Hello world."

=begin
也可以像 python 直接輸入運算式
=end
puts 10+10

然後執行 ruby sample.rb 就可以看到結果。

  • 可以注意到單行註解是用井字號來表示的,跟 yaml 一樣。
  • 多行註解則是要使用等號 begin/end 來表示,但大部分的情況下還是使用井號來表示比較好看一點。

変数/定数

變數宣告不用加上任何單字,只要使用等於即可:

  • 変数名 =式
# 前面不需要寫 var 等關鍵字做宣告
message = "hello ruby."
puts message

而常數的部分,基本上是將變數名稱改大寫開頭就行,但是在 ruby 中,大寫開頭的常數宣告一樣是可以去改變的,只是會報警告而已不會出錯,這點跟 python 一樣。

例如這樣的寫法:

VARIABLE = 1
VARIABLE = 2
puts VARIABLE

執行後會看到報警告:

"C:\Program Files\Ruby\3.2.2\bin\ruby.exe" H:/IntelliJ_Workspace/RubyPractice/lib/hensuu.rb
2
H:/IntelliJ_Workspace/RubyPractice/lib/hensuu.rb:9: warning: already initialized constant VARIABLE
H:/IntelliJ_Workspace/RubyPractice/lib/hensuu.rb:8: warning: previous definition of VARIABLE was here

另外關鍵字的部分可以參考官方文件:字句構造

Literal リテラル

所謂 literal 指的是可以直接寫在 Code 中表示的文字,舉例子比較好理解,有以下種類:

  1. String
    • “message”
  2. 數字
    • 123
  3. 陣列
    • [‘a’,‘b’,‘c’]
  4. 關聯陣列 – 連想配列:也就是 Map,在 Ruby 中也被稱作 Hash
    • { “name” => “John”, “age” => 30 }

數值型別

在 Ruby 中,數字相關的類別有以下幾種:

  1. Integer:整數類別,用於表示整數值。
  2. Float:浮點數類別,用於表示帶小數的數值。
  3. BigDecimal:高精度十進制類別,用於表示需要高精度計算的數值。
  4. Rational:有理數類別,用於表示分數形式的數值。
  5. Complex:複數類別,用於表示複數形式的數值。

由於 Ruby 在宣告變數時就能自動判定型別,關於整數與小數的部分只有插在有沒有小數點而已。

想要查看物件的類別與其擁有的方法,可以使用 class 與 methods:

# 列出型別
puts 1.class
# 列出 Interger 的方法
puts 1.methods.join ","

String

字串宣告分成兩種,雙括號與單括號,這與 Groovy 的 GString/String 是一樣的。

如果要將變數、換行特殊符號等置入字串中,需要使用雙括號才可以:

name = "John"
puts "Hello, #{name}!"

而且 String 在 Ruby 中是 Mutable 跟 Java 不一樣,是可以直接修改原本字串內容而不是新建字串:

str = "Hello, world!"
str[0] = "J"
puts str # => "Jello, world!"

而針對多行字串的寫法,Ruby 使用像是 Linux 中的here document 寫法:

text = <<~EOF
  This is a multi-line text.
  It can contain multiple lines.
EOF
puts text

字串與數字之間的轉換

Ruby 對於型別間無法像 Javascript 一樣自動轉換,需要使用轉換方法才可以:

# 需要使用 to_? 系列方法才能統一型別
puts 'Number is '+ 1.to_s
puts 1 + '2'.to_i

Exclamation mark methhod

可以參考以下文章:[ Ruby ] Methods 後面有!驚嘆號 (exclamation marks)

Ruby 中可以發現有些方法成對的出現,分為有無驚嘆號在方法後面。

有驚嘆號的方法代表的是『危險版本』方法,提示使用者該方法作用會改變物件本身,也就是 mutable 的方法操作。 反之沒有驚嘆號的表示結果會回傳的是新的物件,也就是 immutable 的方法。

驚嘆號本身並不會改變方法的行為。它只是一個命名慣例而已,作為方法名稱的一部分。

# frozen_string_literal: false

message = 'hello'

# 反轉卻未改變物件本身
puts message.reverse
puts message # hello

# 反轉作用在物件上
puts message.reverse!
puts message # olleh

含有問號在方法名後面

在 Ruby 中,方法名後面加上問號(?)通常表示該方法返回的是一個 Boolean(true 或 false)。這種命名慣例用於表達方法的返回值是一個問題或條件的答案。

例如,如果有一個方法名為 empty? ,它可能用於檢查物件是否為空,返回值為 true 或 false。

演算子

參考官方文件:演算子式

大部分應該都與其他語言沒什麼差異。

運算子可以複寫,但也不是全部都可以,詳細看文件。

i++、i– 不存在

Ruby 沒這種寫法,請改用 i += 1 之類的寫法。

流程控制

先說明 Ruby 中,那些物件是屬於 True、那些又是 False。

基本上原則是 false 與 nil 屬於 false,其他都是 true

屬於 True 的物件有以下:

  1. true
  2. 所有數字,包含 -1、0 等
  3. 所有的 String,包含像空字串等皆是。

and/or/not 與 &&/||/! 的差異

Ruby 的運算子有多了 and/or/not 可以使用,這三個比起一般的符號相比優先度會比較低。兩類最好不要混用以免悲劇,請用括號。

詳細用法文件有寫。

条件分岐 if

num = (rand * 10).to_i
result = if num <= 3
           "low"
         elsif num <= 6
           "medium"
         else
           "high"
         end
puts result
  • 這裡的語法在 else if 部分有點不一樣,是 elsif,請注意。
  • 不需要括號包裹條件,但是也可以加上去沒差。
  • Ruby 跟 Kotlin 一樣,if 都是表達式 ( expression ),擁有返回值,也就是你可以直接把結果賦予到變數中。

条件分岐 unless

只要視為 if 的反面用法即可,但是沒有類似對應 else if 的用法,else 還是有。

考慮到語意,unless 如果很難懂就不要應用。

num = (rand * 10).to_i
unless num > 5
  puts "num<=5"
else
  puts "num>5"
end

条件分岐 case

num = (rand * 10).to_i
isZero = case num
         when 0
           true
         else
           false
         end
puts "is zero ? => #{isZero}"

繰り返しの処理 for / each

array = ('a'..'z').to_a

# 一般 each 寫法
array.each do |letter|
  puts letter
end

# 單行 each 簡潔性寫法,要用大括號
array.each { |letter| puts letter }
  • 兩種寫法根據可讀性來選擇
# マップの場合
map = {
  a: 100,
  b: 200
}
puts ""

# 改成兩個參數:key, Value
map.each do |letter, weight|
  puts "#{letter} is #{weight}"
end

array = ('a'..'z').to_a

# for 寫法
for letter in array do
  print letter
end

兩種方法在功能上是一樣的,但是原則上 Ruby 官方指南表示能用 each 就用 each,for 基本上不用除非你有明確的理由,但這也不是強制性的。

繰り返しの処理 times

如果今天要做的迴圈,跟 Collection 沒有任何關係,單存的指定迴圈次數則可以使用 times 來做迴圈。

# times 直接使用要執行的次數來表示,後面的 num 可以省略,會是從 0 開始。
5.times do |num|
  print num
end
  • |num| 可以省略

繰り返しの処理 while

# while
numArray = (0..5).to_a
while !numArray.empty? do
  print numArray.pop
end

繰り返しの処理 upto / downto

前面提到 times 方法可以在從 0 開始去做迴圈,但是如果今天要從一個非零開始去做的話,可以使用 upto 或 downto 來處理。

# upto
-2.upto(2) do |num|
  print "[#{num}]"
end
puts ""
# downto
2.downto(-2) do |num|
  print "[#{num}]"
end

繰り返しの処理 step

如果不要一次 1 變動的話,可以用 step

# Step
0.step(10,2) do |num|
  print num
end

繰り返しの処理 loop、break、next

如果要使用無窮迴圈的寫法,可以用 loop 搭配 break、next 做控制。

index = 0
loop do
  index += 1
  # next 相當於 Continue
  next if index % 2 == 0
  # 終止條件
  break if index > 10
  print "[#{index}]"
end
  • 這裡的 if 條件是單行的寫法。

方法

使用 def 關鍵字宣告。 關於 return 在最後一行會自動返回,不需要明確加上 return 關鍵字。

def say_hello(name)
  message = "hello ,#{name}"
  puts message
  message.size
end
puts say_hello("John")

印出方法比較:puts、print、p、pp

參考以下表格:

有返回值 沒有返回值(nil)
有換行 pp print
無換行 p puts

如果要看效果,用 IRB 在 console 上看會比較清楚。

配列

陣列的部分其實大概可以跟 kotlin 等語言差不多的建立法,有 Range 物件等等的寫法。

其他一些針對陣列的排序、加入/移除等操作也都有對應的方法可用。

# frozen_string_literal: true

# Array 的方法
array = ["a","b","c"]
print array
puts ""

puts "Element B => #{array.include?('b')}"
print array.reverse
puts ""

puts "Empty? => #{array.empty?}"
puts "Shuffle => #{array.shuffle}"

# Array 的建立:透過 Range 物件
# to.a 為轉 Array
puts (0..1).class # Range

print (0..10).to_a
puts ""

print ('a'..'z').to_a
puts ""

# Array 的元素加入/移除類
target = (0..5).to_a

# 加入
target << 6
target.push(7)
print target
puts ""

# 移除
target.pop
print target
puts ""

target.shift
print target
puts ""

Hash

也就是 Map。

前面提到的陣列可以用中括號宣告,而 Map 則是使用大括號搭配箭頭符號來寫。

而在 Key – Value 的分隔寫法上會有兩種寫法

  1. 箭頭符號 =>
    • 傳統寫法
  2. 冒號 :
    • 這是稱為符號(Symbol)分隔符號。
# 舊寫法
hash = { "key" => "value" }
# 新寫法:ruby 1.9 後支援
hash = { key: "value" }

這兩種方式在功能上是等價的,都可以用於宣告 Hash。 但使用冒號的方式更加簡潔和直觀,並且在某些情況下可以提高可讀性。但如果 Key 需要使用特殊符號或包含空格等情況,則必須使用箭頭符號的方式。 總結來說 => 符號和 : 符號可以互換使用,但冒號會更加簡潔和常見。

# 大括號宣告初始值,兩種寫法
my_map = {
  'a' => 100,
  'b' => 200,
  'c' => 300
}
my_map2 = {
  a: 10,
  b: 20,
  c: 30
}

print my_map
puts ""
print my_map2
puts ""

# 操作 Map
puts "key = a => #{my_map['a']}"
puts "key = a => #{my_map2[:a]}"

my_map['d'] = 400
print my_map
puts ""

print my_map2.keys
puts ""

puts my_map.has_key?('a')
  • 這裡注意使用冒號來分隔的話,冒號必須緊鄰 key,中間不能有空格。

類別 Class

針對類別的定義,基本上也就是寫法的問題而已了:

class Cat

  # 常數定義,不可再改變
  TAG = 'Animal'

  # 類別變數宣告
  @@cat_index = 0

  # 快速 Getter/Setter 宣告
  # 可以不用額外自訂 Getter/Setter
  attr_accessor :name

  # 建構子,new 方法會直接調用
  def initialize(name, weight = 10)
    @name = name
    @weight = weight
    @@cat_index += 1
  end

  # Getter 方法 (weight)
  def weight
    @weight
  end

  # Setter 方法 (weight)
  def weight=(value)
    @weight = value
  end

  # 一般方法
  def shout(name = "??")
    puts "hello #{name}, my name is #{@name}"
  end

  # 相當於靜態方法
  def self.index
    @@cat_index
  end

end

my_cat = Cat.new("King")
my_cat.shout
puts my_cat.weight
my_cat.name = "John"
my_cat.shout("Jack")
your_cat = Cat.new("Queen")
puts Cat.index

継承

繼承也跟其他語言差不多,注意一下語法即可:

class User
  def initialize(name)
    @name = name
  end

  def say_hello()
    puts "Hello, my name is #{@name}."
  end
end

class AdminUser < User

  # 可以直接 override
  def say_hello
    super
    puts "(*AdminUser*)"
  end

end

admin = AdminUser.new("God")
admin.say_hello

這裡補充一下,Integer 的類別繼承關係:

  • Integer
  • Numberic
  • Object
  • BasicObject:最上層的類別

另外查詢該類別的父類別可以直接使用 superclass 方法。

puts AdminUser.superclass

Module

這裡簡單介紹 Module 的概念:

特性 Class Module
定義 用於創建物件的藍圖 用於組織和封裝代碼
繼承 支持繼承,可以繼承其他 Class 的屬性和方法 不能被繼承,但可以被包含到 Class 中
實例化 可以實例化為具體的物件,擁有自己的狀態和行為 不能被實例化,只能被包含到 Class 中
命名空間 可用於創建命名空間,將相關的方法和常量組織在一起 可用於命名空間管理,可以被包含到其他 Module 或 Class 中
module Greeting
  def say_hello
    puts "Hello!"
  end
end
class Person
  # 直接引用進 Class 中使用,減少 Code 重複。
  include Greeting
end
person = Person.new
person.say_hello

例外と例外処理

這裡稍微整理了 Ruby 與 Kotlin/Java 在一些語法上的差異:

Ruby Kotlin
拋出異常 使用 raise 關鍵字 使用 throw 關鍵字
捕獲異常 使用 begin – rescue – end 區塊 使用 try – catch – finally 區塊
異常類別 繼承自 Exception 或其他父類別 繼承自 Throwable 或其他父類別
預設捕獲 所有異常都可以被捕獲,除非明確重新拋出 只有被宣告的異常類別或其父類別的異常才能被捕獲
多重捕獲 可以使用多個 rescue 區塊來捕獲不同類型的異常 可以使用多個 catch 區塊來捕獲不同類型的異常
自定義異常 可以創建自定義的異常類別 可以創建自定義的異常類別
def check_age(age)
  raise ArgumentError, "年齡不能為負數" if age < 0
  raise TypeError, "年齡必須是整數" unless age.is_a?(Integer)
  puts "年齡正確:#{age}"
end

begin
  check_age(-5)
rescue ArgumentError => e
  puts "發生 ArgumentError:#{e.message}"
rescue TypeError => e
  puts "發生 TypeError:#{e.message}"
ensure
  puts "END=="
end
  • ensure 是 finally 的用法。

Coding Rule

官方其實沒有一個 Coding 規則,但是用於檢核 Coding Style 的工具 RuboCop 的作者,有提供一個名為『The Ruby Style Guide』的指引提供作為參考。

Link:

這一個指引寫的規則就算不遵守也沒差,但是這能統一提高程式可讀性,建議還是可以參考。

メソードの公開範囲

private、protected 和 public 關鍵字的用法,基本上與其他語言一樣。

寫法上是使用類似區塊的方式,同一區的方法都會是 protected 等等,而預設的方法都會是 public。

class MyClass
  def public_method
    puts "This is a public method"
  end

  protected

  def protected_method
    puts "This is a protected method"
  end

  private

  def private_method
    puts "This is a private method"
  end
end

obj = MyClass.new
obj.public_method # 可以被訪問
obj.protected_method # 無法直接訪問,會出現錯誤
obj.private_method # 無法直接訪問,會出現錯誤

結語

至此簡易的 Ruby 學習已經完成了。 RubyOnRails 將於新的筆記中再行記述。

發佈留言