原來 Ruby 和 DSL 很有淵源啊(喃喃)。
剛學沒多久教授就提到了,Ruby 主要是 SmallTalk + Lisp 佐一點 C。
On the path to enlightenment.
wait to refer:
Ruby Guides done!
Lots of docs
Basic
跟 Python 一樣是動態、腳本語言,所謂變數只是 name binding,
(Philosophy 可是很不同呢~)
用法跟 Python 也一樣, assignment 即宣告。
every thing is object。
basic special operator1
22 ** 4 # 16
[1 , 2 , 3] <=> [3 , 1] # cmp recursively
Comment
1 | # single line comment |
Functions
!
語法糖,將 Method 返回結果回存 Object。.
,function chainning 在 OO 可以玩得很溜。
IO1
2
3
4
5
6gets.chomp
# chomp 去除前後空白字元,包括 \n \t 一類
puts "#{var} with new line"
print "#{var} without new line"
array object’s methods1
2
3arr = [1 , 4 , 2 , 5 , 3]
arr.sort!
arr.sort! { |a , b| -(a <=> b) } # lambda is great!
string object’s methods1
2
3
4
5
6
7
8
9
10
11
12# 不用加括弧啊
"string".to_sym # symbol will explain later
"string".intern # "string" => :symbol
"string".length
"string".reverse
"string".upcase
"string".downcase
"string".capitalize
"string".include? "des to search" # 回傳 bool,很有 lisp style 的 ?
"string".gsub(/replace/ , "new string")
"string".gsub!(/replace/ , "new string") # 直接取代,我只是要強調 ! 的位置
"string".split # split string to array of string
define a function1
2
3
4
5
6
7
8def functionName(argA , argB)
# do something
end
# 由於靈活的語法,可以 omit ()
def add x , y
x + y # omit return will take last exp as return value
end
Control Flow
基本的東西,稍微記一下。
1 | if 1 == 1 |
Loop
1 | while exp |
關於 loop 的流程控制
1 | { # block of loop |
Array
1 | array = [1 , 2 , 3 , 4] |
Hash
1 | hashTb = { "one" => 1 , "two" => 2 , "three" => 3 } |
Symbol
這個資料型態也很潮,
跟字串不一樣,相同的字面值只佔一個空間。
(因為這個特性,所以做 hash 比較優秀嗎?)1
2
3
4
5
6
7
8
9
10
11
12
13puts "string".object_id
# 12886316620
puts "string".object_id
# 12886316480
puts :symbol.object_id
# 801628
puts :symbol.object_id
# 801628
sym = :my_symbol # : 後面要連起來
s_sym = sym.to_s # 轉成 string
Symbol vs String in Hash from Codecademy
1 | require 'benchmark' |
Zen of Ruby (Improvement of Syntax)
one line 系列,不用 end。1
2
3puts "She will accept me" if she_love_me
puts "She will reject me" unless she_love_me
swich case in c , select case in vb , case when in ruby!
非常之靈活。
注意 then and else(without then)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# 基本用法,像 C
case lang
when "java" then puts "so long..." # add then if whole command in one line
when "cpp"
puts "so powerful..." # or omit then with a new line
when "ruby"
puts "so fun!"
else puts "I like the else!"
end
case lang
when "chinese" , "english" # 逗號
puts "I can"
else
puts "I can't"
end
case num
when 1..10 then puts "1 to 10" # GNU extension 也有
when 11..20 then puts "2 to 20"
end
case # 如果 case 沒給,甚至可以像 VB 用 expr
when false then puts "false"
when 1 + 1 == 2 then puts "true"
end
magic operator! (for me)1
2
3
4
5
6contain = nil
puts contain
contain ||= "not nil" # re assignment if nil
puts contain
contain ||= "re assignment" # won't re assignment because it's not nil
puts contain
zen of iterations1
2
3
4
5
6
7
8
9object of num
5.times{ |n| somthing.. }
object of list , map or other
obj.each{ |a , ... | somthing... }
object which can enum (num , char ...)
5.upto(6){ |x| puts x }
'a'.upto('z'){ |x| puts x }
check if obj will response the method or not1
obj.respond_to?(:func) # take care , it take a functino name(symbol) as param
pushy ( <<
operator )1
2
3
4
5
6
7[1 , 2 , 3 , 4].push(5)
# equal to
[1 , 2 , 3 , 4] << 5
"asdf " + "asdf"
# equal to
"asdf " << "asdf"
string interpolation1
puts "list to string #{[1 , 2 , 3 , 4, 5]}" # eq to to_s function
ternary operator1
true ? 1 : 2 # like c
Implicit Return
Lisp 的感覺,當在 let 執行多個 expression 會回傳最後一個 expression。
Ruby 是將 function 看成 let 回傳最後一個 statement。
Blocks , Procs , and Lambdas
前面看了這麼多東西,應該可以察覺 lambda 的概念在哪了吧,
沒錯,就是 block !(然而這不是 Ruby 真正的 lambda ,他還有個 lambda obj){ |x| x * 2 }
和\x -> x * 2
in Haskell 和lambda x : x * 2
in Python 和(lambda (x) (* x 2))
in Scheme 和function(x) { return x * 2 }
in JavaScript 都是 lambda。
不知不覺也學了好多 lambda 呢,不過我在思考可不可以直接拿來 apply 這件事。
Haskell 和 Scheme 不用說,Python 可,JavaScript 和 Ruby 目前不會用。1
lambda{ |x| puts x }.call(8) # Ruby
1 | (function(x){ console.log(x); })(8) // JavaScript |
High Order Function1
2
3# collect 就是 FP 中常用的 map
[1 , 2 , 3].collect { |n| n % 2 == 0 }
[1 , 2 , 3].map { |n| n % 2 == 0 } # 也有 map 這個 function
yield => 傳入的 lambda function 代名詞(變數名)。
一般我們 FP 在定義 High Order Function 時都要為 take 的 function 取變數名。
但 Ruby 在只有傳入一個 function 時,可以直接拿 yield 做關鍵字來 take function。1
2
3
4
5
6
7
8
9# 我做一個簡單的 map
def myMap li
rtn = []
li.each{ |e| rtn << yield(e) }
rtn # Implicit return
end
puts "#{myMap [1 , 2 , 3 , 4] { |x| x ** 2 }}"
# 藉由 ruby 強大的 DSL 能力,括號少很多
Proc To DRY
感覺可以用 JavaScript 的 lambda fn 和其 name binding 體會一下差異1
2
3
4
5
6
7# ruby 要將 lambda(Proc) 綁到變數上,注意 P 大寫
mul = Proc.new { |a , b| a * b } # create a block and => new proc object
# proc obj 比 block obj 成熟
# 且可以用 call method 重複呼叫
# block 是一次性的
puts mul.call 1 , 2
# 因為是物件導向 Proc 物件要當 Function 用要用 call
1 | mul = function(x , y){ return x * y } |
Proc to Block
可以體會到,把 Block 綁到 Proc 是常用的手段,
但要 Proc 如何用在 High Order Function 呢?
前面我們學到 Block 使用是在 function 後加 Block 然後使用 yield 關鍵字。1
2
3
4
5
6
7
8用前面的 myMap 再加一個 double 為例子。
double = Proc.new { |x| x * 2 }
puts myMap([1 , 2 , 3] , &double) # & 號 做的就是把 Proc => Block
# equal to
puts myMap([1 , 2 , 3]) { |x| x * 2 }
# but
puts myMap([1 , 2 , 3] , { |x| x * 2 }) # error
puts myMap([1 , 2 , 3]) &double # error
Proc 應用在 map 上。1
2
3num_s = [1 , 2 , 3].map(&:to_s)
# 注意冒號,要用 symbol 轉 proc 來 call
# (其實我在想是不是 obj method 的關係)
補:Codecademy 上沒有講,但我覺得很奇怪的一點-難道我只能用 yield 嗎?
FP 上都可以把 function 傳進來用變數表示,Ruby 要怎麼做?1
2
3
4
5
6
7
8
9
10
11
12# 這樣做(重寫 myMap)
def myMap li , &fn # 記得傳進來要當 proc or lambda 物件來看了
rtn = []
li.each{ |e| rtn << fn.call(e) } # 所以要用 call method
rtn # Implicit return
end
double = Proc.new{ |e| e * 2 }
#以下兩種都可以過
puts "#{myMap([1 , 2 , 3 , 4] , &double}"
puts "#{myMap([1 , 2 , 3 , 4]){ |e| e * 2 }}"
所以看起來 yield 的優勢是將它當成 function 看。
而這種方式則是當作 object 看。
真正要介紹 Ruby 的 lambda 了,由於是 OO 語言,所以 lambda 是 obj。
用法基本和 proc 相似。1
2proc_double = Proc.new { |x| x * 2 }
lambda_double = lambda { |x| x * 2 }
difference between lambda and Proc
- lambda 會檢查 param 數量,Proc 若沒吃夠直接當 nil
- lambda 會返回控制權, Proc 回直接執行
(對 2. 我有一套自己的解釋法但不知對不對,詳見例)
1 | def ProcTst |
應該只有我會這麼亂想吧…1
2
3
4
5
6
7
8
9
10def ReturnTst
yield # 所以該行回傳結果是 "inner Lbda"
return "ReturnTst" # 然後 return "LbdaTst"
end
puts ReturnTst{ return "rtn of block" }
puts ReturnTst &Proc.new{ return "rtn of Proc" }
puts ReturnTst &lambda{ return "rtn of lambda" }
# => 結果都是 unexpect return
結語,要用一次性且只有一個的 function 用 block + yield 就好
然後一次性多個 就傳 lambda or Proc object 加上 & 吧。
OOP
Capitalize your class name1
2
3
4class Dog # => 強制大寫,不然不給過,立意良善
end
doge = Dog.new "lion" # create a instance
constructor - initialize
他的建構子不是拿 class 的名字
不過說實在的,我覺得 initialize 有點冗長…1
2
3
4
5class Dog
def initialize name
@name = name # 詳見下例
end
end
variable type - global , class , instance1
2
3
4
5
6
7
8$animal_num = 0 # 全域變數,帶 $ 字頭
class Dog
@@dog_count = 0 # 屬於 class 的 var,就是 static 啦,帶 @@ 字頭
def initialize name
@name = name # 帶 @代表成員變數,哼哼不用煩惱變數就是爽
@@dog_count += 1
end
end
function belongs class -> self.fn or ClassName.fn1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Dog
@@dog_count = 0 # 屬於 class 的 var,就是 static 啦,帶 @@ 字頭
def initialize name
@name = name # 帶 @代表成員變數,哼哼不用煩惱變數就是爽
end
# class function 就是 static 的 function 啦
def self.get_dog_num; @@dog_count end
# 意會一下用 ; 而不用換行(想一想為啥 end 前不用呢)
=begin 或者可以寫成
def Dog.get_dog_num
@@dog_count
end
=end
end
inheritance with <
1 | class Animal |
需要 overriide ,因為 doge 叫不出來。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Dog < Animal
@@dog_count = 0 # 屬於 class 的 var,就是 static 啦,帶 @@ 字頭
def initialize name
@name = name # 帶 @代表成員變數,哼哼不用煩惱變數就是爽
# @sound = "Wolf!" # 除了 overriide,也可以將 sound 直接改掉
end
def self.get_dog_num # 就是 static 的 function 啦
@@dog_count
end
def howling # overriide howling
puts "Wolf!"
end
end
doge = Dog.new "lion"
doge.howling # doge 叫出來了
super => 調用 parent 的 同名 function(不只是 initialize 喔)1
2
3
4
5
6
7
8
9
10
11
12
13
14class Dog < Animal
@@dog_count = 0 # 屬於 class 的 var,就是 static 啦,帶 @@ 字頭
def initialize name
@name = name # 帶 @代表成員變數,哼哼不用煩惱變數就是爽
super "Wolf!" # 剛剛的直接取代不漂亮,直接調用 Animal 的 initialize
end
def self.get_dog_num # 就是 static 的 function 啦
@@dog_count
end
end
doge = Dog.new "lion"
doge.howling # doge 叫出來了
Ruby 不支援多重繼承,因為 so ugly,他用 mixin(mod + class)!
public , private , protect
class var , instance var 預設都是 private
class function , instance function 預設都是 public
protect 就是繼承者可用嘛,不多說。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Dog < Animal
@@dog_count = 0 # 屬於 class 的 var,就是 static 啦,帶 @@ 字頭
public
def initialize name; @name = name; super "Wolf!" end
def self.get_dog_num; @@dog_count end
private
def any_fn_you_want_to_be_private; end
end
# or
class Dog < Animal
@@dog_count = 0 # 屬於 class 的 var,就是 static 啦,帶 @@ 字頭
def initialize name; @name = name; super "Wolf!" end
def self.get_dog_num; @@dog_count end
def any_fn_you_want_to_be_public; end
def any_fn_you_want_to_be_private; end
public :any_fn_you_want_to_be_public # 注意是用 symbol
private :any_fn_you_want_to_be_private
# 對 class fn 做 public , private 比較特別
# 要使用 private_class_method 這個關鍵字
# 在此略過不討論
end
attr_reader , attr_writer , attr_accessor
我們常常在 OOP 使用 get 和 set ,但實在太冗了,
Ruby 給我們自動製造 function 的方法。用起來和 public 變數一樣。
1 | class Dog |
Module
module Name => 一樣 Capitalized
module 只能有 Const(大寫的 name)
namespace 呼叫 (和 C++ 一樣可以用 ::
)
require ModuleName (就是 import in python 啦)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require "date" # 可以使用 Date module 的東西,注意小寫及 string
module Rectangle
Edge = 4 # 廢話
def Rectangle.area wid , len
len * wid
end
def Rectangle.perimeter wid , len
(len + wid) * 2
end
def shape
"Rectangle"
end
end
puts Rectangle::Edge
puts Rectangle.area 5 , 10
include module in class
mixin -> imitating multiple inheritance1
2
3
4
5
6
7
8
9class Paper # Ummm...
include Rectangle
def initialize len , wid
@len = len
@wid = wid
end
end
puts m.shape # Rectangle
但我在想 Rectangle 的 area 如果寫成 instance fn 的話
給 Paper inheritance ,area 怎麼直接用到 Paper 的 wid & len 呢…
extend -> as class fn
拿 parent 的 instance fn 當自己的 class fn1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16module Rectangle
Edge = 4 # 廢話
def area wid , len # 注意 area , class fn => instance fn
len * wid
end
def shape
"Rectangle"
end
end
module Square # class 也可以用 extend 喔,效果一樣
extend Rectangle
end
puts Square.area 5 , 5 # 就是這樣
結語,其實我覺得還是有許多問題的,不過這真的是 OOP 面的問題。
寫在後面
Ruby 還有一些比較特別的東西,記在最後面。
單件方法 (singleton method)
就是 instance create 後,可以直接重新 def instName.func。
據說是從 prototype-based 來的。(JavaScript 就是 prototype-based)
恩,有點 WTF 的東西…
感覺整個 obj_new 是個 lambda…
v 是 obj_new closure 裡的 name binding。
1 | def obj_new |
恕我用癟腳的 Scheme 模擬一下。
最近剛好在教 FP 模擬 OOP :)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16(define obj-new
(lambda (v)
(list
(lambda () v)
(lambda (x) (set! v x))
)
)
)
(define obj (obj-new 0))
(define obj-r (car obj))
(define obj-w (cadr obj)) ; 沒有 pattern matching 真麻煩
(display (obj-r)) (newline)
(obj-w 5)
(display (obj-r)) (newline)
其實如果不要照 Ruby 的範例,我 scheme 是比較想寫成
1 | (define obj-new |
例外處理
對應 C++ , Java 的 throw , catch ,Ruby 有著 begin , rescue 。
1 | def get_file_first_line |
inspect func — 自定義直接呼叫物件的回傳值。
1 | class Org |