開開心心學 Ruby 活動筆記

Post Directory

Intro

常常會滑 Accupass 有什麼新的 workshop,剛好看到了 開開心心學 Ruby,所以帶著正在轉職路上的女友一起報名了,本來只是想要體會一下不同語言間有什麼特性差異,但意外地學習到很多,這邊簡單紀錄一下今天活動的收穫

本日重點

從學校一路學上來,不管是跟著教科書,還是跟著教授,只會跟你說

這邊這樣寫,會得到這樣的結果

但往往沒有思考過,那為什麼這邊要寫這樣呢?

整天的課程幾乎都圍繞著

丟出疑問 => 同學們提出想法 => 講師的解答 => 恍然大悟或是感到一些 shock

的迴圈當中,很多現在熟悉的寫法或是用法,其實背後都是有原因的

我覺得相當的有趣

nil?

nil.nil?
# true

所以 nil 還存在嗎?其實從這就可以看出 ruby 語言其實不像程式語言,更像是英文

那當 nil 回傳 存在(true)。那這本身就很抽象

一個代表不存在,且存在的實體物件

另外再 Javascript 中也有類似的東西

NaN => "not a number"

但是

typeof NaN

'number'

Why?

1/’a’ 因為還是為數字,但計算後不是數字 所以回傳一個 number 物件,來告訴你這部是數字

變數

為何會需要變數?

眾人答案:

而講師的回答是

在眾多人中要怎麼指出某個人?你的姓名

常數

在 ruby 中第一個字大寫,的變數就是常數

但是可以複寫,只會跳出 Warning

換成看各位比較熟悉的 Javascript

const list = [1, 2, 3, 4]
list[0] = 'x'

list
// ['x', 2, 3, 4]


// 那重新塞值呢
list = [1, 2, 3, 4]
// 1476:1 Uncaught TypeError: Assignment to constant variable.
// at <anonymous>:1:6

So Why?

其實回到上面的宣告可以看出端倪

const list = [1, 2, 3, 4]

這邊解讀起來是什麼?

宣告,記憶體位置,assign 資料

OK,也許你有猜到了

對於某些來說 const 的設計其實代表的是不能 re-assign 的變數

函數/方法

可能會聽到以下兩種

差異是什麼?

如何『解釋』『說出』?

這邊沒有記錄到當時的討論,然後講師繼續往下解釋 function

以數學來說

f(x) = 3x + 2

轉換簡單轉成程式碼

function aaa(x) {
    return 3 * x + 2
}

而這邊講師對於 function 的定義做了一些解釋

定義輸入值與輸出值的關係

f(x) = 3x + 2
2 --> 8
3 --> 11
4 --> 14

但是 function 隨著一些業務邏輯或是其他操作往往會出現 side effect

那其實就有點違反了 function 本身的定義

所以好的 func 不會有 side effect

但理想上不存在,因此盡量放在最後做(分層) 另外也因此誕生了一個新的流派,Functional programming

function 的寫法

def print(msg = 'hi')
  puts msg
end

print('hello world')

另外提一下,ruby 是可以選擇省略括號的

print hello world

又可以再看得出來值,這樣閱讀起來會更像英文

另外提到的特性

age = 20
def age
  return 18
end

# 前面有提到,可以不用括號
# 但這邊有兩個 age
puts age
# 20

我們可以簡單通過錯誤訊息來檢驗這個問題

age = 20
def age
  return 18
end

puts agesss # 改一下奇怪的不存在變數
main.rb:18:in `<main>': undefined local variable or method `agesss' for main:Object (NameError)

puts agesss
     ^^^^^^

我們可以從錯誤訊息可以看出,先找 variable 再找 method

puts age

所以按照上面的規則

age variable -> age method

所以會是印出 20

那如果要讓他印出 18 呢?

puts age()  # 把括號補回來

如果 function 回傳值為 boolean

ruby 慣例會寫上 ?

def is_audit?()
[].empty?
> true

[1, 2, 3].empty?
> false

也許看起來很怪?

不過如果用英文來看 empty? ,這是個問句,只會回傳對或錯

但你在寫 xxxx? 後,一樣可以回傳其他型別,所以只能說是慣例上

簡單來說 empty 在這邊是動詞還是名詞?而在 ruby 的設計來看就是讓它可以識別

def adult?(age)
    age >= 18
end

需要看手冊,可能發生意外的驚喜

加上 !

def go!()
list = [2, 3, 1, 5]

list.sort

p list

會印出

[2, 3, 1, 5]

你可能會預期是印出有排序的東西

但實際上,只呼叫 func 不應該修改到原本的內容

這樣其實是合理的,因為可能在某個地方呼叫到,而 list 就會被修改

list = [2, 3, 1, 5]

list.sort! # in place

p list

而通常會有 ! 都會有一個正常版本

所以通常代表一個動作,或是需要看手冊

Falsy Values

對 ruby 來說只有

代表 Falsy Values

反過來看 Javascript

就設計來說,Javascript 的設定來說有點多

在一些程式語言設計中

const list = [1, 2, 3, 4]

宣告,記憶體位置,assign 資料

block

遍歷 array

list = [1, 2, 3, 4, 5]
for item in list
  p item
end

# 比較常見寫法
list.each{ |x|
  p x
}

看看 JS

list.forEach((x) => {
    console.log(x)
})

可以類比成

list.forEach(|x| => {
    console.log(x)
})

其他方法

doubleList = list.map do |item|
    return 2 * item
end

p doubleList 
# 這邊不會印出東西
# 因為在 do ... end 中,return 中,會直接整個程式碼 return


doubleList = list.map do |item|
    2 * item
end

p doubleList 
# each 5 -> x (不會回傳)
# map 5 -> 5(會回傳相等的 item)
# filter 5 -> (<=5)
# reduce 5 -> single value

命名

double_list - 蛇式

Symbol

# hero = {:name=>"kk", :age=>18} 
hero = {name: 'kk', age: 18} # > ruby 1.9 寫法

# :xxx <-- Symbol

p hero
# {:name=>"kk", :age=>18}   所以上面寫法本質上還是舊的方式

p hero[:name]  # 因此取值時需要使用 :
# 'kk'

如果有很多層

p hero[:name][:a][:b]

如果裡面的內容有異動,那就會爆炸

# 往下挖,如果挖得到
p hero.dig(:name, :a, :b)

OOP

class(具象類別)是什麼

可以簡單想成生物分類法

沒有 override 而是一層一層往上找

class Animal
  def eat
    p "yummy!"
  end
end

class Cat < Animal
  def eat
    p "123!"
  end
end

kitty = Cat.new
kitty.eat
# "123!"

模組化

假設有奇怪需求,一隻飛天貓

class Animal
  def eat
    p "yummy!"
  end
end

module Flyable
  def fly
    p "fly!"
  end
end

class Cat < Animal
  include Flyable
  
  def eat
    p "123!"
  end
end

class Dog < Animal
end


kitty = Cat.new
kitty.fly
# "fly!"

並不會複製進來,而是往上找時,先找模組找找看

繼承與模組的關係

p Cat.ancestors # 所有祖先
# [Cat, Flyable, Animal, Object, Kernel, BasicObject

p Cat.superclass.superclass.superclass # 該 class 的上一層
# BasicObject

因此可得知 Kernel,也是 module

singleton method

只有該類別存在的方法

下面註解都是等價的

class Cat < Animal
    def self.fly
    # def Cat.fly
        p "fly!!!"
    end
end


# def def Cat.fly ...
kitty = Cat.new
kitty.fly

開放類別

class Cat   
  def eat
    p "123!"
  end
end

class Cat   
  def run
    p "456!"
  end
end

kitty = Cat.new
kitty.eat
kitty.run

這邊算是 ruby 的特性,如果存在相同 Class 宣告

通常會想到的可能性

但在 ruby 中他允許重複,且會融合內部的 function

如果兩個都有一樣的 function ,才會後面複寫前面

那也因為這個特性,所以可以為原始類別加上方法

class String
    def valid_email?
        true
    end
end

p "kk@gmail.com".valid_email?

而在 Rails 時中又有更多這類型的 function

在 Rails 中就可以看到

針對取 array 這件事,時做了一堆而外的 function

# 取第一個 item
p [ "q", "r", "s", "t" ][0]
# q

# 而在 Rails 中有人實作了
p [ "q", "r", "s", "t" ].first
# q

那他一路做到了 fifth

那就有人偷嘴說,這麼無聊那你怎麼不做個 42

於是就出現了這個

https://apidock.com/rails/Array/forty_two

[1,2,3,4].forty_two

這個特性很有趣

舉裡: 我現在要取前一天的

回到我熟悉的語言 Golang 中

time.Now().Add(-24 * time.Hour)

那回到 ruby 中可以加上 function 後達到

Time.now.ago(24.hours)

因該可以看的出來哪個更像英文吧?

回到對於常數的思考

如果常數能夠改變,那為何還需要常數?

class cat
end
main.rb:7: class/module name must be CONSTANT
class cat
exit status 1

看得出來嗎?class 命名是常數,還記得前面提到大寫自首為常數

也因為常數可以被修改,才造就了 開放類別 這個類性

但為了這個『方便的』開放類別,而讓常數可以被修改

這樣算是個好設計嗎?

結語

以下是我的心得:

可以看出講師想要帶給我們的不是,教你這個東西就「這樣」寫就好了

而是程式語言被「設計」,一定有一個明確的原因目的,或是時空背景

也可以藉此反思,那如果是你來設計,你又會怎麼設計?

前同事有一句話一值留在我腦裡

「軟體工程師的價值,在於抽象化的能力」

而今天走完整個 Workshop 後,我的體悟是

程式語言本身就是個設計,那這些設計的哲學是否能幫助你未來開發上,幫上忙呢?

所以要多思考為什麼? 而不是只是簡單填充程式碼的碼農

Tweet

發表評論前可以看一下 Pixiv 網站日排行榜,壓壓驚

source: https://github.com/mokeyjay/Pixiv-daily-ranking-widget