標籤:
代碼塊在其他的語言中都或多或少接觸過一些,如perl中sort{$a<=>$b}keys,傳入代碼塊實現按數值排序,在swift中用到閉包,更加深入學習到training closure、capturing value等代碼風格,對代碼塊有了深入的瞭解,並且意識到代碼塊是參考型別(Reference Type),和Value Type有所區別,意識到代碼塊和類、方法等的相似之處。
在學習Ruby的過程中,對代碼塊的理解更加加深一步,不僅僅是簡化代碼的功能,還涉及到範圍、可調用對象等知識。
代碼塊
代碼塊定義在大括弧或者do...end關鍵字中,塊被傳遞到方法中時,可以以yield關鍵字回調,同時能夠通過Kernel#block_given?()方法檢測是否傳入了代碼塊:
class MyClass
? ? def my_method
? ? ? ? return yield if block_given?
? ? ? ? "There‘s no block"
? ? end
end
obj = MyClass.new
puts obj.my_method{"There‘s a block!"}
puts obj.my_method
塊也可以有自己的參數,在||中用逗號隔開,調用時用yield(arg)就能調用:
def my_method
? ? return yield(2,3)
end
puts my_method {|x,y| "The two numbers are #{x} and #{y}”}
以上語句會輸出 The two numbers are 2 and 3
綁定
塊不僅僅可以通過調用參數的方式和其他語句交流,更加重要的是,塊在定義的時候,會從當前範圍擷取到綁定:
def my_method
? ? return yield(2,3)
end
z = 4
puts my_method {|x,y| "The there numbers are #{x},#{y} and #{z}"}
這理解起來並不難:在過程語句中,每一條語句都可以訪問到當前範圍的其他變數,塊也是一樣,把可以啟動並執行代碼看做兩部分組成:代碼本身和一組綁定。
如果還是有些困惑的話,是因為表示代碼塊的{}並不意味著新的範圍,請注意Ruby中是沒有{}表示開始結束的,而是用了類似python的風格。
範圍
那麼在Ruby中,是什麼真正影響了範圍呢?其實只有三個關鍵字會開啟新的範圍,稱為“範圍門”:
class、module、def
一旦碰到這三個關鍵字的其中一個,就意味著進入了新的範圍,並且在end關鍵字出現時離開,這裡可以理解為範圍門就是左大括弧{,end可以看成右大括弧},如果你願意這麼理解的話。class、module和def有微妙的差別:class和module定義中的代碼會被立即執行,而def定義中的代碼只有被調用的時候才會被執行。
由於範圍僅僅由這三個關鍵字來決定,那麼可以通過一些方法穿越範圍門,而讓代碼塊獲得上方範圍的綁定:用Class.new()、Module.new、Module#define_method()方法來代替class、module和def關鍵字:
var = "top level obj"
MyClass = Class.new do
? ? define_method :my_method do
? ? ? ? puts var
? ? end
end
obj = MyClass.new
obj.my_method
如上述代碼,在my_method中任然可以訪問到var,而不會受到範圍門的阻隔,這種方法成為扁平範圍,通過利用範圍門和穿越範圍門,可以靈活地共用範圍以及範圍保護。
打破封裝
在範圍中,還有一種黑科技:Object#instance_eval()方法。這個方法可以傳遞一個塊,作為上下文探針,該方法調用的塊的接收者會成為self,此時,該塊可以訪問接收者的私人方法和執行個體變數,甚至可以在不碰其他的綁定情況下,修改self對象:
class MyClass
? ? def init
? ? ? ? @v = 1
? ? end
? ? attr_reader :v
end
obj = MyClass.new
obj.instance_eval {
? ? @v = 2
}
puts obj.v
此時,obj的v屬性已經被修改了!所以為什麼稱它為黑科技了,因為他可以打破封裝結構,同時它還有另外的作用,製作潔淨室。
潔淨室是那些只為了執行其中的代碼塊的類:
class CleanRoom
? ? def complex_calculation
? ? ? ? #…
? ? end
? ?
? ? def do_something
? ? ? ? #…
? ? end
end
clean_room = CleanRoom.new
clean_room.instance_eval{
? ? if complex_calculation > 10
? ? ? ? do_something
? ? end
}?
以上就是一個潔淨室的例子
可調用對象
塊的使用過程分為兩步:打包和調用。將塊打包後方便以後調用的方法有三種:proc、lambda、使用方法。
在Ruby中,絕大多數東西都是對象,但是塊除外,如果想要將塊打包儲存,則需要調用一些類來擷取協助。
Proc就是其中的一種類。在建立Proc類時,會將調用的Block Storage,並在之後可以通過call來調用:
obj = Proc.new{"This is a block"}
puts obj.call
除了Proc.new之外,Ruby還提供兩個核心方法用來將塊轉換成Proc:proc()、lambda(),其中proc()可以看成是Proc.new的一個別名(Ruby1.9之後),他們的區別是lambda更加像一個方法,他的return會從lambda中返回,而proc僅僅表示從當前範圍中返回,以及其他的一些不是很清楚的區別。
和在swift中類似,代碼塊可以看做是方法的最後一個隱含參數,也可以顯示地命名它,要求使用&符號開頭(這裡沒有任何聯絡,但是我記憶的時候將他理解為:因為塊是參考型別,所以用&符號傳入引用):
def my_method(a,b,&operation)
? ? #...
end
my_method(1,2){
? ? #...
}?
此時,用&命名也是一種儲存塊的方式,通過這種方式可以讓塊的內容在多個方法之間傳遞,或者將這個塊傳遞給另外一個Proc。
&符號的真實含義:將一個Proc對象作為塊來使用,簡單地去掉&,就可以再次獲得Proc對象。直到了這點,也可以在其他地方,利用&Proc來將Proc轉換回一個塊。
?
Ruby學習之代碼塊