Enumerator <> 一旦上了船,程式就是不會背叛你的夥伴

[Day30] 還在等什麼!快來成為 Ruby 工程師啊!(誤)

終於來到鐵人賽的最後一天了!以為我要寫個完賽心得帶過嗎?雖然很想這樣做,不過,這裡並不是停下腳步的地方啊!

這篇要介紹的是 Enumerator


Enumerator 是什麼?

剛開始看到這個詞只覺得陌生 + 困惑

上網查了許久,Enumerator 似乎沒有一個很精準的中文翻譯,但可以找到像是 external, iterator, Enumerable Module 這些關鍵字。

但其實它一直都在我們身邊。

平時 coding 可能就已經看過它,那就是當使用 each 方法操作陣列相關的物件時,又不小心忘了寫 Block 的話:

1
2
[1,2,3].each
=> #<Enumerator: [1, 2, 3]:each>

就會出現這個 Enumerator ! 可是···這是什麼意思?還是不懂。

沒關係把 Enumerator 放一邊,先來看看 Enumerable 能不能輔助我理解。


Enumerable 又是什麼?

雖然和上一個概念相比,似乎只有單字詞性的變化,不過 Enumerable 的概念明確許多。

首先,Enumerable 是一種模組(Module),又可以稱為「列舉」,來瞧瞧 Ruby 官方文件的定義:

列舉提供陣列和雜湊搜尋、排序或轉換集合內元素的功能。而陣列或雜湊必須各自提供一個方法,該方法可以產生集合的連續成員···(?)

簡單來說,像是 each, select, map, match, sort, grep 這些常用的方法都屬於 Enumerable 的範疇。

詳細內容請參考 Ruby-Doc Module: Enumerable


External Iterator

接下來介紹外部迭代器(External Iterator)

大部分時候我們都在做的是 Internal Iterator
像是平常在丟迴圈,或是上在介紹的陣列方法,都屬於 Internal Iterator ,譬如想從一個陣列中選取一些元素:

1
2
3
4
5
6
7
list = [1, 2, 3, 4]

list.select do |n|
n > 2
end

#=> [3, 4]

不過,既然有 Internal ,也就有 ExternalInternal Iterator 是由陣列或雜湊這類的集合物件控制,而 External Iterator 則是由外部來控制,像是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
icons = %w[up down].to_enum(:cycle)
list = [1, 2, 3, 4, 5]

list.each do
puts icons.next
end

# =>
up
down
up
down
up
=> [1, 2, 3, 4, 5]

在這段程式碼裡,icons 被指定使用 cycle 方式在做迭代,所以當 next 到最後一個元素,下一次的 next 就會從頭再來過。


所以,什麼是 Enumerator?

我這邊引用五倍紅寶石 DC 助教的原話:

Enumerator 最簡單的解釋是 Enumerable 加上 External Iteration,而 EnumeratorEnumerable 的合作方式是前者負責產生、儲存資料,『需要時』再以後者的方法運用。

譬如:

1
2
3
4
5
list = [1,2,3].each
#=> #<Enumerator: [1, 2, 3]:each>

list.select { |x| x.odd? }
#=> [1, 3]

除了這種彈性的用法之外,Enumerator 的另一個特性是能把物件不斷串起來,以最近超紅的電影《天能》為例:

1
2
3
4
5
6
7
8
9
'TENET'.split('').reverse_each.with_index(1) do |word, index|
puts "#{word}"
end
T
E
N
E
T
=> ["T", "E", "N", "E", "T"]

果然是天能!時光回溯!


結語

Enumerator 的概念不是很好理解,在尋找文獻和研究的過程中不斷撞牆,似乎勉勉強強地看懂了一些,但仍拼湊不出全貌,不過身為工程師,主動接觸一些不熟悉的概念是應該的。

之後的文章應該會放在 Medium 或是自己架的部落格,期許往後的路上還要學得更多,並時常接觸自己不熟悉的領域以及運用所學,天啊!這種既掙扎又興奮的感覺真是太棒了!

那,鐵人賽就先到這邊啦!
在出發的那一刻,我們就已經得到 ONEPIECE 了!


參考來源
與你每天擦身而過的 Enumerator

Singleton Class 與物件導向 <> 記錄指針不見了!我們該如何前進到下個島嶼

[Day29] 記錄指針是海賊王世界裡一種外觀像手錶,中央為球形的特殊羅盤。可以偵測並記下偉大航路內各個島嶼放出的磁場,為海上航行的船隻提供正確的路線。

延續昨天的主題,我們已經知道單體方法是怎麼被定義的,但它既不屬於類別方法,也不是實體方法,那它到底被定義在哪裡呢?

先說結論:單體方法被寫在 Singleton Class 裡。

走過的路 是一陣魔術···
···都化作這目光 吟唱成一首歌

抱歉突然想唱個歌,讓我們進入正題~


Singleton Class 是什麼?

如果是初學 Ruby 這門程式語言的朋友,一開始幾乎不太會接觸到 Singleton Class 的概念。在一般的繼承鍊裡,我們只學過 Class < Object < BaseObject 這種一直往上找,層層相疊的繼承關係。

不過實際上在 Ruby 的物件導向設計裡,還有一個非常神祕的類別,
那就是 Singleton Class

  • 它是設計給單一物件使用的類別
  • 又被稱為 metaclasseigenclass
  • Singleton Class 上定義的方法就是單體方法

回顧昨天的例子:

1
2
3
4
5
6
7
8
9
10
11
class Cat
# ...
end

meme = Cat.new

def meme.feet
puts "有穿白襪"
end

meme.feet # => 有穿白襪

雖然單體方法 feet 是寫在類別的外面,但 Ruby 其實有偷偷為 meme 準備了一個 Singleton Class ,而 feet 方法就被定義在裡面:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Cat
# ...
end

meme = Cat.new

# singleton class of meme
def meme.feet
puts "有穿白襪"
end
# end

meme.feet # => 有穿白襪

有看到 Singleton Class 嗎?

沒看到?

嗯··· 代表你非常正常!

通常在開了天眼後才看得到

所以泰安老師叫它「跟鬼一樣的類別」!


我要在哪裡找到 Singleton Class

其實我還不知道如何在 irb 裡顯示物件的 Singleton Class,不過,通過類別物件的繼承或許可以看出一點端倪。

繼承最核心的概念就是子層類別的物件可以拿父層類別的方法來用,類別方法也同樣遵循著這套繼承的規則

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal
def self.all
puts "全部的動物"
end
end

class Cat < Animal
def self.all
puts "貓咪王國!"
end
end

Cat.all # => 貓咪王國!

如果有同名的類別方法,則會優先呼叫定義在 Singleton Class 的那個。

昨天有提到,類別方法其實也是單體方法的一種,所以每個類別的類別方法就會定義在自己的 Singleton Class 上,像是這樣:

現在我們知道了,當一個 Ruby 物件在呼叫方法時,會先往 Singlton Class 找看看,沒有的話,會再往上從繼承的類別裡尋找。


Ruby 裡的物件導向:

既然都已經講了 Singleton Class,那就不得不來介紹 Ruby 裡的物件導向了!

「物件導向」這四個字看起來又是個高大上,一開始總覺得如果能說清楚這個概念,好像自己會變很聰明,但事實上,它就只是個語言的設計概念。(另一個事實則是我笨)

接下來會用到這兩個方法:
|方法|說明
|—|—|
|superclass| 尋找自己繼承的父層類別
|class| 尋找自己所屬的類別

superclassclass 能有助於更加釐清 Singleton Class 以及 Ruby 裡物件導向的概念。

繼續沿用剛才的例子,並用這兩個方法來看看吧!

1
2
3
4
5
6
7
8
class Cat
# ...
end

meme = Cat.new

meme.class # => Cat
meme.superclass # => NoMethodError (undefined method `superclass' for #<Cat:0x00007fd482123eb0>)

咦? superclass 噴錯了!不過沒關係,因為實體物件本來就沒有繼承的父層類別,繼續來看:

1
2
Cat.class               # => Class
Cat.superclass # => Object

Cat 這個類別是屬於一個叫做 Class 的類別,或者說,在 Ruby 裡,所有的類別都屬於 Class 這個類別。

Cat 這個類別是繼承自一個叫做 Object 的類別,”Object”,也就是物件的意思,以一個物件導向的語言來說,出現這個名字很合理,繼續看下去:

我們來對 Class 試試看吧:

1
2
Class.class             # => Class
Class.superclass # => Module

這次出現了有趣的事情,果然全天下的類別都是屬於 Class 這個類別,因此 Class 所屬的類別也是 Class 自己。

Class 繼承的類別居然是 Module ! 這不是模組嗎?不過仔細一瞧才發現大寫的 Module 是類別,小寫的 module 則是模組,只是,Ruby 設計者沒想過會讓大家困惑嗎 XDDD

做一個模組出來試試看:

1
2
3
4
5
module Flyable
end

Flyable.class # => Module
Flyable.superclass # => NoMethodError (undefined method `superclass' for Flyable:Module)

Flyable 這個模組屬於 Module 這個類別,而 Flyable 沒有向上的繼承類別(這是因為 Ruby 裡的模組本來就無法繼承)

但令人驚訝的是 Class 竟然是繼承自 Module !當初在學習時,就有發現模組和類別兩個概念很像,但我原本一直以為模組可能是類別的副產品之類的,

沒想到···竟然是反過來
真是世事難料啊!!!

接著我們來看看這個 Object

1
2
Object.class            # => Class
Object.superclass # => BasicObject

不出所料,Object 的類別是 Class,而繼承自 BasicObject 類別這個又是什麼?名字怎麼都取這麼像啊!

1
2
BasicObject.class       # => Class
BasicObject.superclass # => nil

BasicObject 的類別也是 Class,而再往上就沒有了!

目前可以得到像這樣的圖:

最後來看 Module

1
2
Module.class            # => Class
Module.superclass # => Object

嗯···類別是 Class,然後繼承自···咦!Object!那不就

1
2
3
Object.class            # => Class
Class.superclass # => Module
Module.superclass # => Object

你們這樣繞來繞去,弄得我頭很痛啊!還好網路上已經有大大梳理好它們彼此之間的關係

看圖是不是清楚多了呢?
說實話,韓劇每個角色的關係圖都比這都複雜多了!


今天的文章就先到這裡了,

以上就是我對 Ruby 物件導向的理解,自己覺得這篇的脈絡有點亂,就和思緒一樣,可能也有一些理解錯誤的地方,大大們如果看到的話還請不吝指出,日後還會繼續進行釐清和整理的。

覺得好像感冒了…頭好昏啊!


參考來源
Metaprogramming in Ruby 2 - The Object Model
speedred - Ruby 的繼承鍊 (1) - 如何實踐物件導向

Singleton Method <> 是「百獸海賊團」!碰上四皇凱多有勝算嗎?

https://ithelp.ithome.com.tw/upload/images/20201006/20128363CAkf2qfYdC.jpg

[Day28] 今天登場的是媲美侏羅紀公園的百獸海賊團!幹部三災個個雄壯威武,船長凱多更被譽為海賊王世界最強生物!

今日圖已端上,那麼話不多說
這篇要介紹的是:單體方法(Singleton Method)


什麼是單體方法

第一次聽到 singleton 這個詞是在泰安老師的課上,記得那個時候一邊聽一邊心裡卻在吶喊著:「奇怪明明就是說中文啊···為什麼我有聽沒有懂···」(狀態表示為三觀盡毀)

直到現在才比較明白了,簡單來說,單體方法是一種專屬於某個物件的方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
class Cat
# ...
end

meme = Cat.new
didi = Cat.new

def meme.feet # singleton method
puts "有穿白襪"
end

meme.feet # => 有穿白襪
didi.feet # => NoMethodError (undefined method `feet' for #<Cat:0x00007fd2359aa790>)

可以看到這邊定義的 feet 方法只能給 meme 這個實體物件使用,而其他的實體物件,就算是同一類別產生的實體,都無法使用 feet 方法,

由此可知,我們可以在任意物件上定義單體方法,不過前提是要先有那個物件存在,不然就會看到噴錯:

1
2
3
4
5
6
7
8
9
10
11
class Cat
# ...
end

def meme.feet # singleton method
puts "有穿白襪"
end

meme = Cat.new

meme.feet # => NameError (undefined local variable or method `meme' for main:Object)

看起來好像跟類別方法有點像?

沒錯!類別方法其實也是一種單體方法,之前介紹到類別方法時,會這樣寫:

1
2
3
4
5
6
7
class Crew
def self.all
puts "全員到齊"
end
end

Crew.all # => "全員到齊"

在這裡,我們可以把 self 換成這個類別 Crew (昨天有提到 self 指向目前正在執行的物件),所以這樣寫也是可以的:

1
2
3
4
5
6
7
class Crew
def Crew.all
puts "全員到齊"
end
end

Crew.all # => "全員到齊"

又或者這樣,在類別裡執行類別方法也是可以的:

1
2
3
4
5
6
7
8
9
10
class Crew
def Crew.all
puts "全員到齊"
end

all
end

# 印出
全員到齊

單體方法被定義在哪裡?

在一開始的例子裡,我看到 meme 可以呼叫我自己定義的 feet 方法,但仍然有些不解,為什麼這個方法被定義在類別的外面,卻又只能給特定的實體取用呢?

我們先看一般的方法定義:

1
2
3
4
5
6
7
8
9
10
11
def eat
"肚子餓了就要吃東西"
end

class Cat
end

meme = Cat.new

meme.eat # => "肚子餓了就要吃東西"
didi.eat # => "肚子餓了就要吃東西"

嗯···明顯不一樣,揪竟單體方法是被定義在哪裡呢?
meme.feet 又做了什麼呢?


今天到這邊先告個段落,要理解艱澀的概念,我們必須得休息一會喝個水然後睡個覺,明天會來繼續討論 singleton class 這個主題!

Self <> 真正的男子漢,連道別都不需要語言!

[Day27] 就算身處絕境也要堅守自己的原則!

入秋有點涼意了,伴隨著晚風,我們不如來點小酒,聊聊有關自己的事~
今夜不醉不歸

不鬧了不鬧了這篇真的是要聊自己,不過是聊的是 Ruby 語言裡,就算天塌了也會永遠做自己的 self


self 是什麼?

之前提到類別方法時,我們就已經看過 self 了(還記得貝洛貝蒂嗎?)

1
2
3
4
5
6
7
8
9
class Army
def fight # 實體方法
puts "我要打倒天龍人!"
end

def self.inspire # 類別方法
puts "全部的人都起身戰鬥!"
end
end

self 是一種 Ruby 的虛擬變數(pseudo-variable),先來看看它屬於哪一類

1
2
self.class
=> Object

好!確定身分了!self 是個物件!

不過這個物件有點特別,self 會永遠指向當前正在執行的物件。(喔?似乎和 JavaScript 的 this 有點類似?)

在類別裡,self 代表目前的類別:

1
2
3
4
5
6
7
8
class Treasure
def self.self # 類別方法
self
end
end

Treasure.self
=> Treasure

在實體裡,self 則代表目前的實體:

1
2
3
4
5
6
7
8
def self
self
end

captain = "魯夫"

captain.self
=> "魯夫"

什麼都沒有直接用 self 會是什麼?

想知道的話我們用 who_am_i 這個方法來看:

1
2
3
4
5
6
def who_am_i
self
end

who_am_i
=> main

印出了 main !這個 main 是什麼呢?別小看它,它是 Ruby 裡層級頗高的一個物件,它也是直接隸屬於 Object 這個類別下的實體。
(原來跟 self 是同事啊!)

main 的存在非常有趣,之前提過在物件導向的概念裡,呼叫方法會需要 receiver,而 Ruby 之所以能直接呼叫方法,是因為在這個環境裡,其實已經先偷偷做好了一顆物件,因此如果沒有指定任何物件,這顆物件就會指向 main


self 可以用來區分方法和變數

在 Ruby 裡

1
2
3
4
5
6
7
8
9
10
11
12
13
class Pirate
def supernew
"最惡世代"
end

def difference
supernew = "超新星"
puts supernew # 印出變數
puts self.supernew # 印出實體方法
end
end

Pirate.new.difference

在 Ruby 裡,同時存在同名的變數和方法時,會以變數優先,但若真的需要呼叫方法,而需要忽略變數的話,可以使用 self 來達成。


也可以把 self 當作回傳值使用

肚子有點餓了,想吃個點心:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Snack
attr_accessor :bowl

def initialize
@bowl = []
end

# 把穀片倒入碗裡
def add_cereal
@bowl << :cereal
self
end

# 把牛奶倒入碗裡
def add_milk
@bowl << :milk
self
end

# ... 其他點心
end

現在來盛一碗享用吧!

1
my_snack = Snack.new.add_cereal.add_milk

來看看碗裡有什麼:

1
2
3
4
my_snack.bowl

# 印出
[:cereal, :milk]

嗯~太棒了!是我喜歡的穀片 + 牛奶~開動!(嚼嚼嚼)


今天就先到這邊啦~真的嘴饞打算去吃個點心
而且關於自己的事也不好講太多(

如果想了解更多 Ruby 的 self ,網路上有更多大大們分享的精彩文章,都非常值得一看喔!


參考來源

  1. 尋找自己: Ruby 的 self 物件與 singleton method
  2. 什麼是 Self & 怎麼使用它
  3. Ruby 裡的 self 是什麼意思
  4. Ruby self 與作用域
  5. Ruby 中的 self 變數以及應用
  6. Ruby 初心者的學習筆記 2:難以捉摸的 self
  7. Ruby 裡的 self 是什麼意思

鴨子型別 <> 終於抵達阿拉巴斯坦王國!迎接我們的是 · · · 卡魯鴨?

[Day26] 卡魯鴨:我跑遍了沙漠,好渴啊!

自從學了 Ruby ,就常聽到別人在說 **鴨子型別(Duck Type)**,可是卻一直沒有弄得很懂,今天決定來了解什麼是鴨子型別!

開始囉!


型別

型別,是一種為了讓資源(位元組)更有效率地被利用而規劃的概念。簡單來說,每筆資料都有自己所屬的型別,譬如:整數、字串、陣列等等...

鐵人賽第3天裡,我列出了一些 Ruby 常見的資料型別,各自做了簡單的介紹。但型別遠沒有這麼簡單啊!

Ruby 常被稱作是一種強型別的語言,然而,有強就有弱,判定標準在於語言能否自動轉換型態:像是 JavaScript,經常會偷偷地把變數轉換成可以被執行的資料型態(讓我覺得十分困擾)不過到了 Ruby 這裡,就會被仔細地檢查型別,型別不符的話則會發生錯誤。

還記得用怎麼類別產生實體物件嗎?產生實體物件也是一種將某個型別實體化的過程。

1
2
3
4
5
String.new("卡魯鴨")
=> "卡魯鴨"

String.new(123)
TypeError (no implicit conversion of Integer into String)

動態型別?

接著,我們可能還要了解「動態型別」是什麼。

Ruby 被認為一種 動態型別(Dynamically Typed) 的語言,「動態、不固定」的意思是 在命名變數時,不用先宣告是哪種型別

有了動態型別,我們就能寫出較為簡潔的程式碼。同時,也因為不用先提供型態資訊,因此變數型態是可以改變的。

舉個例子,比方說先把字串"卡魯鴨" 指定給區域變數 a

1
2
3
4
5
6
7
a = String.new("卡魯鴨")
=> "卡魯鴨"

a = 123

a
=> 123

變數 a 的型別被換成整數(Integer)了!


鴨子?型別?

終於要切入今天重點了!

鴨子型別是一種程式設計的風格。這種風格認為,一個物件的型別不是由這個物件所屬的類別決定,而是由「它能做什麼」來決定。

如果你看到一隻鳥走起來像鴨子,游泳起來像鴨子;叫起來也像鴨子;那麼這隻鳥就可以被稱為鴨子。

換句話說,想知道一個物件是什麼型別,看它能使用什麼方法就好。

接下來用程式碼來解釋,現在有 CatDog 兩個類別,彼此沒有相互繼承關係。

1
2
3
4
5
6
7
8
9
10
11
class Cat
def hungry # 同樣名稱的方法
puts "喵~肚子餓了!"
end
end

class Dog
def hungry # 同樣名稱的方法
puts "汪!肚子餓了!"
end
end

再建立一個 Pet 類別,同時定義一個 who_is_hungry 方法可以傳入 name,並讓 name 呼叫 hungry 執行:

1
2
3
4
5
6
7
8
class Pet
def initialize
end

def who_is_hungry(name)
name.hungry
end
end

接著做出幾個實體:

1
2
3
miru = Cat.new
wolf = Dog.new
pet = Pet.new

現在讓 miruwolf 當作帶入的參數試試:

1
2
3
4
5
6
pet.who_is_hungry(miru)
pet.who_is_hungry(wolf)

# 印出
喵~肚子餓了!
汪!肚子餓了!

用其他參數或另外的實體試試:

1
2
3
4
5
6
7
pet.who_is_hungry(123)
=>NoMethodError (undefined method `hungry' for 123:Integer)

fish = Pet.new

pet.who_is_hungry(fish)
=> NoMethodError (undefined method `hungry' for #<Pet:0x00007fd74407d0a0>)

可以看到 pet.who_is_hungry 在傳入 miruwolf 的當下,並沒有要求要檢查它們的型別,但是會確認它們都能執行 hungry 方法才能正常通過!

真是盡責的小鴨呢!


關於鴨子型別的的介紹就先到這邊啦!謝謝大家的觀看~

例外處理 <> 白鬍子登場!就連海軍大將都無法擊倒的那個男人!

https://ithelp.ithome.com.tw/upload/images/20201003/20128363cDiBrBzRjS.jpg

[Day25] 就算是海軍本部又怎麼樣,艾斯,老爹來救你了!

這篇要介紹的是 Ruby 裡的例外處理(Rescue Exception)

每天都在 coding 的我遇到噴錯已經是家常便飯,而這些錯誤可能是環境、是語法、是邏輯矛盾等等各式各樣的問題所造成。

然而,工程師除了要解決碰到的每個錯誤,有時候甚至可以「未卜先知」*,如果在撰寫程式碼時,就已經先猜到可能會有錯,並告知程式在特定錯誤發生時該如何處理,就能防範於未然,省下噴錯後還得去找問題的時間。

*聽說厲害的工程師還會「通靈」!


什麼是例外處理?

簡單來說,當程式執行時出現了某個預料外的結果,基本上會有兩種情況:

  1. 如果沒有提供 rescue 的話,執行中止
  2. 執行 rescue 裡的程式碼

預料外的結果?

「人生不如意事十之八九,程式也會有。」

我們寫程式時總是可能會寫錯,所以噴錯是非常正常的。rubylearning.com 列出了各種可能會產生例外的情況,像是我們很熟悉的SyntaxErrorArgumentErrorRuntimeError 等等…

附上完整版供參:

這時,就是例外處理(Rescue Exception)登場救火的時機了!

先來看看怎麼寫:

1
2
3
4
5
6
7
begin
# 發生了某個錯誤
rescue
# 這個錯誤出現時要怎麼處理
ensure # (optional)
# 無論有沒有發生例外,這一段都一定會執行
end

(Ruby 在這裡充分發揮了它的強項:程式碼非常好讀啊!)

整體運作有點像條件判斷,如果沒有出現例外,就不會進到 rescue

  • beginrescue 是正常執行時的情況
  • 一旦無法執行,就會將控制權轉移到 rescue
  • 執行 rescue 以下到 end 的區域

begin 來自於 Object 類別,基本上和 end 一起使用,主要是用來處理例外產生的機制,常被統稱為 begin block 或是 begin-end block


噴錯也是一種例外處理的方式

平常 Ruby 噴錯所顯示的訊息,實際上也是執行錯誤後產生的一種例外處理。直接看 code 最清楚:

1
2
3
4
5
6
7
8
9
10
11
12
begin
# 可能出現例外的程式碼
rescue RuntimeError
# 出現RuntimeError時執行
puts "RuntimeErrorr"
rescue SyntaxError
# 出現SyntaxError時執行
puts "SyntaxError"
rescue ArgumentError
# 出現ArgumentError時執行
puts "ArgumentError"
end

(這個很重要!沒有的話我就不知道噴錯的原因了!)


raise 自己做出錯誤訊息

接著再來看個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def raise_exception
puts "準備執行 raise"
raise "久等啦我就是錯誤訊息!"
puts "結束執行 raise"
end
=> :raise_exception

raise_exception

# 印出
準備執行 raise
Traceback (most recent call last):
5: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>'
4: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load'
3: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
2: from (irb):17
1: from (irb):14:in `raise_exception'
RuntimeError (久等啦我就是錯誤訊息!)

從上面例子裡可以看到, "結束執行 raise" 這行並沒有被印出來,原因是我用 raise 方法觸發了例外處理,使得程式在執行時將控制權交給了 rescue

raise 是什麼?
一個來自於 Kernel 模組的方法,用途是做出一個例外實體,預設是 RuntimeError

要做出其他錯誤類型也行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def raise_exception
puts "準備執行 raise"
raise SyntaxError, "你好像寫錯什麼了雖然我不確定"
puts "結束執行 raise"
end

raise_exception
準備執行 raise
Traceback (most recent call last):
5: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>'
4: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load'
3: from /Users/jrnalts/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
2: from (irb):29
1: from (irb):26:in `raise_exception'
SyntaxError (你好像寫錯什麼了雖然我不確定)

把所有的 rescue 都集中起來吧!

如果同時有很多地方需要 rescue,我們可以透過 rescue from 把所有例外集中處理,這樣就不用每個地方都寫 rescue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ApplicationController < ActionController::Base
rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
rescue_from ActiveRecord::RecordInvalid, :with => :show_errors

rescue_from 'MyAppError::Base' do |exception|
render :xml => exception, :status => 500
end

protected
def deny_access
...
end

def show_errors(exception)
exception.record.new_record? ? ...
end
end

感覺方便很多呢!


例外處理的 beginend 都可以省略

如果有個例外處理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def raise_exception
begin
puts '咦...是不是?'
raise '有大事發生啦!'
puts '哇哇哇!'
rescue
puts '沒事~哥已經處理好了'
end
end

raise_exception

# 印出
咦...是不是?
沒事~哥已經處理好了
=> nil

在這個凡事都有哥處理好的例子裡,如果我們省略了 beginend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def raise_exception
puts '咦...是不是?'
raise '有大事發生啦!'
puts '哇哇哇!'
rescue
puts '沒事~哥已經處理好了'
end

raise_exception

# 印出
咦...是不是?
沒事~哥已經處理好了
=> nil

程式也是不會壞呢!真的很罩!


今天就先介紹到這邊啦!期許自己平常在 coding 時也能記得要用上 rescue 的寫法,減少程式噴錯抓錯的情況不停發生!

陣列方法 <> 學不會就無法走出這個錯綜複雜的迷宮!路痴索隆表示:

[Day24] 對不起我對陣列方法知道的太少了了!(跪)


今天這篇要向大家介紹一些超級常用的陣列方法(Array Method)

先來介紹居家旅行刷題解扣殺人放火的必備:

  • select
  • map
  • reduce

select

select 可以在陣列裡挑選出「符合條件」的元素。

1
2
3
4
a = %w{ a b c d e f }

# 找出是母音的英文字母
a.select {|v| v =~ /[aeiou]/} #=> ["a", "e"]

%w 是另一種表示陣列的寫法,在這裡例子裡,設定了一個條件陣列[aeiou] 並透過 =~ 來篩選出陣列a 與陣列[aeiou] 相符的元素。


map

map 是對這個陣列裡的每一個元素做某件事之後,再收集成一個新的陣列,通常會在 map 後面接的 Block 裡寫陣列中每一個元素要做的事。

1
2
3
4
5
6
7
8
9
10
11
12
a = [ "a", "b", "c", "d" ]

# 在每個元素後面加上!
a.map {|x| x + "!" } #=> ["a!", "b!", "c!", "d!"]
a #=> ["a", "b", "c", "d"]


b = [ 0, 1, 2 ,4]

# 在每個元素後面加上 2 (要是數字)
b.map {|n| n + 2} #=> [ 2, 3, 4 ,6]
b #=> [ 0, 1, 2 ,4]

也可以使用 collect !會得到一樣的結果。
另外提醒一點,除非是使用 map! 否則原本的陣列是不會改變的!


reduce

reduce 會幫我們把陣列中的每個元素收集起來,拿去運算,最後再告訴我們總計為多少,完全是熱心助人的模範生方法。

1
2
# 計算一個範圍的數字總和
(5..10).reduce(0) { |total, i| total + i } #=> 45

這邊我解釋一下,這裡假定初始值為 0,然後會依序把 5..10 這個範圍裡的 6 個數字: 5, 6, 7, 8, 9, 10 傳進 Block 和初始值相加(因為在這裡是 +),最後得出結果。

因此,也就是說 reduce 其實是把陣列裡的每個元素不斷地「互動」,再把每個互動結果全部搜集後,最終再回傳總計的一個方法

這個例子也可以這樣寫:

1
(5..10).reduce(:+)                             #=> 45

甚至字串也可以

1
["a","b","c"].reduce(:+)                       #=> "abc"

inject 也能做到同樣的事!

1
2
3
4
5
6
7
8
9
# Same using a block and inject
(5..10).inject { |sum, n| sum + n } #=> 45


# find the longest word
longest = %w{ cat sheep bear }.inject do |memo, word|
memo.length > word.length ? memo : word
end
longest #=> "sheep"

接著再來介紹其它也很常用的陣列方法!

slice

簡單來說,slice 就是會依照索引值(index) 來取出陣列裡相對應的元素。

1
2
3
4
5
6
7
8
9
10
11
12
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[5, 1] #=> []
a[5..10] #=> []

join

join 可以幫我們把陣列裡的元素組合起來,想要的話,中間還可以插入我們指定的東西。

1
2
[ "a", "b", "c" ].join        #=> "abc"
[ "a", "b", "c" ].join("-") #=> "a-b-c"

concat

concat 可以幫我們把兩個陣列接起來!

1
[ "a", "b" ].concat( ["c", "d"] ) #=> [ "a", "b", "c", "d" ]

結語

我想身為一個稱職的 Ruby 工程師,為了方便查詢,而把 Ruby API 放在 Chrome 書籤列最顯眼的地方是非常合理的,畢竟,

工欲善其事,必先RTFM」(Read The Fantastic Manual)

但就算看了文件,到了 CodewarLeetcode 上面解題時卻還是

自己對於 Ruby 的方法知道還是太少了!所以今天才想說應該要來複習陣列方法。

只是,陣列方法百百種,使用時機可說是千變萬化,這幾個只是常用的基礎,記得還是要看手冊喔!

如果還有其他常用的陣列方法,歡迎補充在下方留言,希望大家都能順利解題~

empty? nil? blank? <> 聽說今天是滿月?戰鬥力爆表的月亮獅子變身!

[Day23] 加洛特的月亮獅子型態瞬間炸裂全場!

抱歉今天的圖和主題可能關聯性沒這麼強,但選在今天放這張真的再適合不過了!
先祝 IT 邦的各位大大中秋節快樂!


這篇要來繼續弄懂一些自己不是很清楚的東西!
接著我們來比較三個很像的概念

  • empty?
  • nil?
  • blank?

empty?

翻譯:這個是空的嗎?(誰吃完了?)

empty? 是一個用來檢查集合裡是否為空的方法,這裡指的集合包括陣列、雜湊以及字串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 空的陣列
[].empty?
=> true

# 空的雜湊
{}.empty?
=> true

# 空的字串
"".empty?
=> true

# 有東西的字串
" ".empty?
=> false

最後一個 " "empty? 方法檢查後得到 false,原因是字串內含有「空白」這個值,所以並不是真的為空。


nil?

翻譯:我說你,是不存在嗎?(推眼鏡)

一開始學的時候,會覺得 falsenil 很像,後來才發現意思其實差很多。因為 false 代表的是「錯誤」、「假的」,而 nil 則代表「空的」或「不存在」,在其他語言可能是用 null

先把 nil 印出來看看:

1
2
puts nil
=> nil

在 Ruby 裡,所有的物件都不是 nil,而只有 nilnil ,因此在 nil? 方法檢查下:

1
2
3
4
5
6
7
nil.nil?       #true
[].nil? #false
{}.nil? #false
"".nil? #false
" ".nil? #false
"abc".nil? #false
123.nil? #false

nil 一個跟大家不一樣,也太沒人緣了啊!

所以我說,nil 它存在嗎?

這個問法好像在討論哲學議題 :D

nil 在 Ruby 裡其實是真實存在的物件,它只是被用來表示「空的」、「不存在」的概念而已,可以用 nil? 方法來問它是不是 nil (有點詭異)

1
2
nil.nil?
=> true

nil :我就沒存在感了,還特別問我在不在是怎麼樣?當我塑膠膩!

關於 nil 可以參考龍哥的大作 為你自己學 Ruby on Rails ,裡面有更詳細的說明!


blank?

翻譯:沒有帶奇怪的東西進來就行~(睜一隻眼閉一隻眼)

blank? 則是一個較為寬鬆的檢查方法,不管集合內是「真·空」,還是集合內有「空白」,甚至代表不存在的 nilblank? 都可以接受。

1
2
3
4
5
6
7
nil.blank?     #true
[].blank? #true
{}.blank? #true
"".blank? #true
" ".blank? #true
"abc".blank? #false
123.blank? #false

也就是說,前面 empty?nil? 的所要篩選的概念「空的」以及「不存在」,到了 blank? 這裏一律都會回傳 true

blank?:同意啦~哪次不同意~啊等等!有東西的話就真的不行~(追上)


簡單的比較就到這邊啦!是不是覺得似乎有那麼一點更了解了?(正能量上身)

在這邊敬祝大家中秋佳節愉快~

attr_accessor <> 喲嚯嚯嚯~可以讓我看一下你的 code 嗎?

[Day22] 靈魂之王布魯克為您表演~ 45 度角!

學寫程式也有一陣子了,但直到最近才發現很多時候自己都只是知其然而不知其所以然,而沒有完全了解自己在寫的到底是什麼。

所以這篇就要來提一些很常被用到,但還不是非常清楚的東西:

什麼是 getter

顧名思義,就是「取得(某個東西)」的方法,在 Ruby 裡,我們會把取出實體變數這件事稱為 getter ,指的就是取出實體變數的方法。

1
2
3
4
5
6
7
8
9
10
class Cat
def initialize(name)
@name = name
end
end

miru = Cat.new('miru')

miru.name
=> NoMethodError (undefined method `name' for #<Cat:0x00007ff82783a578 @name="miru">)

在類別裡,沒有 getter 就無法取出實體變數的值。所以 miru 這個實體拿不到類別裡的實體變數 @name,如果要拿到 @name 的話,就必須給它一個 getter

1
2
3
4
5
6
7
8
9
10
11
12
13
class Cat
def initialize(name)
@name = name
end

def name #getter method
@name
end
end

miru = Cat.new('miru')
miru.name
=> "miru"

然而,如果每次要取實體變數的值都要重寫一次 getter ,嗯…用想的都覺得麻煩啊!因此超好用的 attr三兄弟 就誕生了!(登愣!)

指令 說明
attr_reader 用來取出實體變數。
attr_writer 用來指派實體變數。
attr_accessor 結合以上兩者,可取出也可指派實體變數。

讚嘆工程師們懶惰的美德!


attr_reader

attr_reader 可以幫助我們取出實體變數。

1
2
3
4
5
6
7
8
9
10
11
class Cat
attr_reader :name

def initialize(name)
@name = name
end
end

miru = Cat.new('miru')
miru.name
=> "miru"

跟剛剛完全一樣!改成用 attr_reader :name ,所以不用再寫:

1
2
3
def name
@name
end

一行抵三行,超級划算!


什麼是 setter

setter 也很好理解,就是「指派」的意思,在 Ruby 裡,我們會定義一個 setter 方法,透過呼叫這個方法,就可以重新指派實體變數。

attr_writer

我們延續剛剛的例子,如果今天有一隻叫做 miru 的貓

1
2
3
4
5
6
7
8
9
10
11
class Cat
attr_reader :name

def initialize(name)
@name = name
end
end

miru = Cat.new('miru')
miru.name
=> "miru"

而我想替祂換個更響亮的名字,結果不管怎麼寫通通都失敗了!

1
2
3
4
5
6
7
8
miru.name"neko"
=> ArgumentError (wrong number of arguments (given 1, expected 0))

miru.name."neko"
=> SyntaxError ((irb):14: syntax error, unexpected tSTRING_BEG)

miru.name = "neko"
=> NoMethodError (undefined method `name=' for #<Cat:0x00007fada005f780 @name="miru">

這是因為,在這個類別裡並沒有 setter ,因此產出的實體變數無法重新指派一個新的值。

那我們試試看加上 setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Cat
attr_reader :name

def initialize(name)
@name = name
end

def name=(name)
@name = name
end
end

miru = Cat.new('miru')
miru.name
=> "miru"

這時候就可以幫 miru 換個更好聽的名字了!

1
2
3
4
miru.name=("neko")
=> "neko"
miru.name
=> "neko"

而由於 Ruby 可以省略小括號的特性,以下這兩種 setter 的寫法可能更常見:

1
2
3
def name= name
@name = name
end
1
2
3
def name = name
@name = name
end

在使用上也可以:

1
2
miru.name = "puma"
=> "puma"

也因為這樣,才會説實體變數也是實體物件的屬性(attribute),這其中大部分的原因就是 setter 造成的!

當然,更懶惰的寫法就是用 attr_writer 啦!

1
2
3
4
5
6
7
8
9
10
11
12
class Cat
attr_reader :name
attr_writer :name

def initialize(name)
@name = name
end
end

miru = Cat.new('miru')
miru.name
=> "miru"

同樣也是一行抵三行啦!


attr_accessor

然而,沒有最懶,只有更懶。

attr_accessor 就是整合了前面兩者效果的神奇好夥伴,用了 attr_accessor 以後程式碼都變得超乾淨!等於一次擁有 attr_readerattr_writer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Cat
attr_accessor :name

def initialize(name)
@name = name
end
end

miru = Cat.new('miru')
miru.name
=> "miru"
miru.name = "wolf"
miru.name
=> "wolf"

是不是很心動!趕快打開 irb 玩玩看吧!

關於 attr_accessor ,大家可以拜讀龍哥這篇 Ruby 語法放大鏡之「attr_accessor 是幹嘛的?」,裡面有更多更專業的說明!


今天就介紹到這邊啦!大家明天見!

實體變數 <> 一流強者都要會的武裝色霸氣

[Day21] 運用武裝色霸氣就能在皮膚表面覆蓋一層堅硬的盔甲,同時提升防禦力和攻擊力!

嗨大家好!今晚想來點「實體變數」~

到目前為止我們所提的變數都是區域變數,而一開始介紹變數時為什麼沒有講到這部分呢?(根本就是忘了)

不過我覺得一旦理解 scope 的概念後,自然而然地就能了解區域變數為什麼叫做「區域」變數了:因為它只能在某個 scope 內生效。

如果有點失憶的朋友可以回到 鐵人賽第 4 天


Ruby 裡有哪些變數?

共分成以下幾種:

類型 範例 預設值 說明
區域變數 name 作用範圍僅限在一個區域裡,方法內外不可相互取用。
實體變數 @name nil 在類別中可以被各個方法取用,也可以成為實體物件的屬性,但不同實體物件的屬性不一定相同,
類別變數 @@name 同一類別建立的實體物件都可以取用,類別不同就無法。
全域變數 $name nil 可以在不同的類別被取用。
常數 Name nil 主要用於類別與常數的命名。

雖然變數有這麼多種,不過由於在實務上很少用到類別變數和全域變數,相對我也沒這麼熟悉,因此這篇將會聚焦介紹 Ruby 的實體變數。


實體變數是什麼?

在 Ruby 裡,我們會在變數的名字前面加一個 @ ,來代表實體變數,也可以當作實體物件的屬性(attribute)。

來看一下定義:

實體變數的 scope 是整個類別,因此它能夠在同一類別的方法之間傳遞,而每一個物件的屬性會各別賦值,不會和其他的物件共享,它會隨著不同的物件而有不同的值。

上面這段文字似乎太抽象了,直接看 code 吧:

1
2
3
4
5
class Cat
def name
@name
end
end

好像還不太夠,再加上 initialize 試試看:

1
2
3
4
5
6
7
8
9
10
class Cat
def initialize(name, color)
@name = name
@color = color
end

def color
"#{@name}是隻#{@color}色的小貓"
end
end

如此一來,我們就可以在 Cat 類別產生新實體的那一刻,自動把參數指定給實體變數,變成專屬於這個實體的屬性,而不會與其他實體共享。

我們來養一隻小貓:

1
2
3
4
5
miru = Cat.new("miru","虎斑")
puts miru.color

# 印出
miru是隻虎斑色的小貓

一隻貓不夠吸,養貓就要養兩隻:

1
2
3
4
5
nini = Cat.new("nini","黑")
puts nini.color

# 印出
nini是隻黑色的小貓

可以看到雖然 mirunini 都使用了同樣的實體變數 @name@color ,但這兩個實體物件各自有屬於自己的屬性,同時,在類別內的實體方法要取用 @name@color 時,也不用再定義這個需要參數。


如果要在類別外取得實體變數?

剛剛有提到,實體變數的 scope 是在整個類別內,不過一旦離開了類別,這個實體變數就不能用了。

因此,如果要在類別外取得這個實體變數,可以這樣做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Dog
class Cat
def initialize(name, color)
@name = name
@color = color
end

def color
"#{@name}是隻#{@color}色的小貓"
end

# getter
def color
@color
end

# setter
def color=(new_color)
@color = new_color
end
end

通常會定義一個同名方法來獲得實體變數的值,這個行為被稱作 getter
而用這個同名方法加上 = 作為另一個方法來重新傳參數給實體變數,這個行為就叫做 setter

1
2
3
4
5
6
7
8
9
10
11
12
didi = Cat.new("didi","橘")
puts didi.color

#印出


# 重新定義值
didi.color= "橘帶一點黃"
puts didi.color

#印出
橘帶一點黃

看到了嗎?我們可以更精準地定義 didi 的顏色啦!


今天就先介紹到這邊啦!關於 gettersetter 的使用方式,明天還會再看到它們!