Ruby Programming/Syntax/Method Calls

上一項: 控制結構 索引 下一項: 類別

方法(Method)

编辑

什麼是方法(method)? 在個體導向編程中,我們不希望外部直接操作個體內的資料;因為個體本身更了解這些資料應如何操作,故透過它提供的方式操作資料較為合適。這麼想吧,我們傳一些訊息給個體,這些訊息將引起個體做出某些動作或有意義的回應。我們只需知道傳什麼訊息可以讓個體做哪些工作,而不需關心個體內部的實際工作內容。那些允許我們要求個體完成或處理訊息的工作,就稱之為個體的方法或行為。「方法」或「行為」這兩種詞彙皆有使用。

在 Ruby 中,我們以句號 (.) 調用個體的方法。個體為主詞置於句號左側,方法為動詞置於句號右側。 Ruby 是在程式運行途中,依變數當前所指涉之個體決定如何調用方法回應訊息。

ruby> "abcdef".length
   6

直覺可知這是在問此字串個體之長度為何。技術上,我們說「調用 "abcdef" 之個體方法 length」。

其他個體對「長度」的詮譯可能有些許差異,或者完全不同。

ruby> foo = "abc"
   "abc"
ruby> foo.length
   3
ruby> foo = ["abcde", "fghij"]
   ["abcde", "fghij"]
ruby> foo.length
   2

長度的意義根據我們所談及的對象不同而有所差異。我們第一次問 foo 之長度時,它指涉一個字串,故長度的意義為此字串的字元數。第二次問長度時,foo指涉一個陣列,在大多數情形下,此時長度的意義是指陣列的內容數量,故答案為 2 。

根據實際指涉之個體以及傳遞之訊息決定工作方法的方式,接近我們使用自然語言時的表達習慣:同一詞語會依對象不同而具有不同的意義。此為個體導向編程的特徵,我們將之稱為多型(polymorphism)

當個體接收到一個它無法理解的訊息時,將喚起(raised)錯誤:

ruby> foo = 5
   5
ruby> foo.length
ERR: (eval):1: undefined method `length' for 5(Fixnum)

故我們需要知道個體接受哪些方法,雖然我們不須知道這些方法如何工作。若方法需要引數(arguments)時,引數通常用小括弧包起,但可以也省略。

If you don't have code that needs to use method result immediately, Ruby allows to specify parameters ommitting parenthesis:

  object.method(arg1, arg2)
  object.method arg1, arg2

  results = method_name parameter1, parameter2           # calling method, not using parentheses
  
  # you need to use parentheses, if you want to work with immediate result from the method
  # if method returned an array, and we want to get element order reversed
  results = method_name(parameter1, parameter2).reverse

Ruby 有一個特殊變數稱為self,它指涉發生行為之個體。因為 我們經常使用 "self.",所以在個體本身內部使用時,我們可以省略它。


self.method_name(args)
#is the same as
method_name(args)

傳統上的函數呼叫(function call)在 Ruby 中亦被視為方法調用。那些函數性的方法仍然具有類似傳統程式語言的函數之行為特徵。在 Ruby 中談到「函數」時,通常表示那些並非真正的個體方法,而是純粹的演算法或抽象的敘述。

Ruby doesn’t really have functions. Rather, it has two slightly different concepts - methods and Procs (which are, as we have seen, simply what other languages call function objects, or functors). Both are blocks of code - methods are bound to Objects, and Procs are bound to the local variables in scope. Their uses are quite different.

Methods are the cornerstone of object-oriented programming, and since Ruby is a pure-OO language (everything is an object), methods are inherent to the nature of Ruby. Methods are the actions Ruby objects do - the messages they receive, if you prefer the message sending idiom.

Procs make powerful functional programming paradigms possible, turning code into a first-class object of Ruby allowing to implement high-order functions. They are very close kin to Lisp’s lambda forms (there’s little doubt about the origin of Ruby’s Proc constructor lambda)

The construct of a block may at first be confusing, but it turns out to be quite simple. A block is, as my metaphor goes, an unborn Proc - it is a Proc in an intermediate state, not bound to anything yet. I think that the simplest way to think about blocks in Ruby, without losing any comprehension, would be to think that blocks are really a form of Procs, and not a separate concept. The only time when we have to think of blocks as slighly different from Procs is the special case when they are passed as the last argument to a method which may then access them using yield.

That’s about it, I guess. I know for sure that the research I conducted for this article cleared many misunderstandings I had about the concepts presented here. I hope others will learn from it as well. If you see anything you don’t agree with - from glaring errors to nitpicky inaccuracies, feel free to comment - I’ll be happy to discuss any remarks and fix my mistakes.


方法定義

编辑

要定義一個方法時,使用def 方法名稱,用end結束。 參數(parameters)方法名稱後面,可用括號包住或不用皆可且多個參數(parameters)使用逗號區隔。

Example:

 def output_something(value)
   puts value 
 end
 def output_something value
   puts value
 end

回傳值

编辑

函式會回傳最後一個執行的敘述的值,下面的範例會回傳x+y的結果:

 def calculate_value(x,y)
   x + y
 end

An explicit return statement can also be used to return from function with a value, prior to the end of the function declaration. This is useful when you want to terminate a loop or return from a function as the result of a conditional expression.

如果你在區塊內使用了return,你將會直接跳出函式,而得到的值可能不是你所想要的。 單純要離開區塊,可以用break

 six = (1..10).each {|i| break i if i > 5}

在這個程式內,six的值將會是6

Default Values

编辑

A default parameter value can be specified during method definition to replace the value of a parameter if it is not passed into the method or the parameter's value is nil.

 def some_method(value='default', arr=[])
   puts value
   puts arr.length
 end
 some_method('something')

The method call above will output:

 something
 0

Variable Length Argument List, Asterisk Operator

编辑

The last parameter of a method may be preceded by an asterisk(*), which is sometimes called the 'splat' operator. This indicates that more parameters may be passed to the function. Those parameters are collected up and an array is created.

 def calculate_value(x,y,*otherValues)
   puts otherValues
 end
 
 calculate_value(1,2,'a','b','c')

In the example above the output would be ['a', 'b', 'c'].

The asterisk operator may also precede an Array argument in a method call. In this case the Array will be expanded and the values passed in as if they were separated by commas.

 arr = ['a','b','c']
 calculate_value(*arr)

has the same result as:

 calculate_value('a','b','c')

Another technique that Ruby allows is to give a Hash when invoking a function, and that gives you best of all worlds: named parameters, and variable argument length.

 def accepts_hash( var )
   print "got: ", var.inspect # will print out what it received
 end
 
 accepts_hash :arg1 => 'giving arg1', :argN => 'giving argN'
 # => got: {:argN=>"giving argN", :arg1=>"giving arg1"}

You see, the arguments to accepts_hash got rolled up into one hash variable. This technique is used in Ruby On Rails API heavily.

Notice missing parens around arguments to accepts_hash function, and notice there is no { } Hash declaration syntax around the :arg1 => '...' code. The above code is equivalent to more verbose:

 accepts_hash( :arg1 => 'giving arg1', :argN => 'giving argN' )  # argument list enclosed in parens
 accepts_hash( { :arg1 => 'giving arg1', :argN => 'giving argN' } ) # hash is explicitly created

Now, if you are going to pass a code block to function, you need parentheses.

 accepts_hash( :arg1 => 'giving arg1', :argN => 'giving argN' )  { |s| puts s }
 accepts_hash( { :arg1 => 'giving arg1', :argN => 'giving argN' } )  { |s| puts s } 
 # second line is more verbose, hash explicitly created, but essentially the same as above

The Ampersand Operator

编辑

Much like the asterisk, the ampersand(&) may precede the last parameter of a function declaration. This indicates that the function expects a code block to be passed in. A Proc object will be created and assigned to the parameter containing the block passed in.

Also similar to the ampersand operator, a Proc object preceded by an ampersand during a method call will be replaced by the block that it contains. Yield may then be used.

 def method_call
   yield
 end
 method_call(&someBlock)

了解 blocks, Procs and methods

编辑

Ruby 可以將程式碼參數化,Ruby 稱被參數化的程式碼為 block (程式碼區塊)。Ruby 語法以 {||} 表示一個程式碼區塊,其中的 || 為參數列宣告,若無參數則可省略。

Ruby 的 Proc 類似 ECMAScript 的 Function。在 ECMAScript 中使用關鍵字 function 即可配置一個 Function 實例。 Ruby 則使用 Kernel::procKernel::lambda 方法,或是直接建構一個 Proc 實例(Proc.new),需提供一個 block 作為引數。

Ruby: proc { |arguments| codes }
ECMAScript: function(arguments) { codes }

Shamelessly ripping from the Ruby documentation, Procs are defined as follows: Proc objects are blocks of code that have been bound to a set of local variables. Once bound, the code may be called in different contexts and still access those variables.

A useful example is also provided:


 def gen_times(factor)
     return Proc.new {|n| n*factor }
 end
 
 times3 = gen_times(3)      # 'factor' is replaced with 3
 times5 = gen_times(5)
 
 times3.call(12)               #=> 36
 times5.call(5)                #=> 25
 times3.call(times5.call(4))   #=> 60

Procs play the role of functions in Ruby. It is more accurate to call them function objects, since like everything in Ruby they are objects. Such objects have a name in the folklore - functors. A functor is defined as an object to be invoked or called as if it were an ordinary function, usually with the same syntax, which is exactly what a Proc is.

From the example and the definition above, it is obvious that Ruby Procs can also act as closures. On Wikipedia, a closure is defined as a function that refers to free variables in its lexical context. Note how closely it maps to the Ruby definition blocks of code that have been bound to a set of local variables.

More on Procs

编辑

Procs in Ruby are first-class objects, since they can be created during runtime, stored in data structures, passed as arguments to other functions and returned as the value of other functions. Actually, the gen_times example demonstrates all of these criteria, except for “passed as arguments to other functions”. This one can be presented as follows:


 def foo (a, b)
     a.call(b)
 end
 
 putser = Proc.new {|x| puts x}
 foo(putser, 34)

There is also a shorthand notation for creating Procs - the Kernel method lambda [2] (we’ll come to methods shortly, but for now assume that a Kernel method is something akin to a global function, which can be called from anywhere in the code). Using lambda the Proc object creation from the previous example can be rewritten as:

 putser = lambda {|x| puts x}

Actually, there are two slight differences between lambda and Proc.new. First, argument checking. The Ruby documentation for lambda states: Equivalent to Proc.new, except the resulting Proc objects check the number of parameters passed when called.. Here is an example to demonstrate this:


 lamb = lambda {|x, y| puts x + y}
 pnew = Proc.new {|x, y| puts x + y}
 # works fine, printing 6
 pnew.call(2, 4, 11)
 
 # throws an ArgumentError
 lamb.call(2, 4, 11)

Second, there is a difference in the way returns are handled from the Proc. A return from Proc.new returns from the enclosing method (acting just like a return from a block, more on this later):


 def try_ret_procnew
     ret = Proc.new { return "Baaam" }
     ret.call
     "This is not reached"
 end
 
 # prints "Baaam"
 puts try_ret_procnew

While return from lambda acts more conventionally, returning to its caller:


 def try_ret_lambda
     ret = lambda { return "Baaam" }
     ret.call
     "This is printed"
 end
 
 # prints "This is printed"
 puts try_ret_lambda

With this in light, I would recommend using lambda instead of Proc.new, unless the behavior of the latter is strictly required. In addition to being way cooler a whopping two characters shorter, its behavior is less surprising.

Ruby's lambda is unusual in that choice of parameter names does affect behavior:

 x = 3
 lambda{|x| "x still refers to the outer variable"}.call(4)
 puts x  # x is now 4, not 3

Simply put, a method is also a block of code. However, unlike Procs, methods are not bound to the local variables around them. Rather, they are bound to some object and have access to its instance variables [3]:


 class Boogy
     def initialize
         @dix = 15
     end
 
     def arbo
         puts "#{@dix} ha\n"
     end
 end
 # initializes an instance of Boogy
 b = Boogy.new
 # prints "15 ha"
 b.arbo

A useful idiom when thinking about methods is sending messages. Given a receiver - an object that has some method defined, we can send it a message - by calling the method, optionally providing some arguments. In the example above, calling arbo is akin to sending a message “arbo”, without arguments. Ruby supports the message sending idiom more directly, by including the send method in class Object (which is the parent of all objects in Ruby). So the following two lines are equivalent to the arbo method call:


 # method/message name is given as a string
 b.send("arbo")
 
 # method/message name is given as a symbol
 b.send(:arbo)

Note that methods can also be defined in the “top-level” scope, not inside any class. For example:


 def say (something)
     puts something
 end
 
 say "Hello"

While it seems that say is “free-standing”, it is not. When methods such as this are defined, Ruby silently tucks them into the Object class. But this doesn’t really matter, and for all practical purposes say can be seen as an independent method. Which is, by the way, just what’s called a “function” in some languages (like C and Perl). The following Proc is, in many ways similar:


 say = lambda {|something| puts something}
 
 say.call("Hello")
 # same effect
 say["Hello"]

The [] construct is a synonym to call in the context of Proc [4]. Methods, however, are more versatile than procs and support a very important feature of Ruby, which I will present right after explaining what blocks are.

Block and Proc

编辑

程式碼區塊無法單獨存在,只能作為 Ruby 指令或呼叫方法時的引數。Ruby 會主動將程式碼區塊參數化,程序員僅需利用流程指令 yield 即可將流程轉移到被參數化的程式碼區塊中運行。

我們亦可透過 Proc 引用程式碼區塊。細節後述。

不論是用程式碼區塊或是 Proc ,都可以用 Kernel::block_given? 方法判斷使用者有無傳遞程式碼區塊。

# Block way. 這種用法較常見。
def nP(n)
    if block_given?
        yield n    # yield to black
    else
        puts 'no block'
    end
end

#用例:
nP('hello') {|n| puts n}
nP(10) do |n|
    n.times do
        puts 'a'
    end
end

#Proc way. 細節後述。

def nP(n, &p)
    if block_given?
        p.call n    # call proc p
    else
        puts 'no block'
    end
end

#用例:
nP('hello') {|n| puts n}
nP(10) do |n|
    n.times do
        puts 'a'
    end
end

ECMAScript 只有 Function 類,沒有 block 與 proc 的區分。上述的 Ruby 程式,以 ECMAScript 表達如下列所示:

function nP(n, p) {
    return p(n);
}

nP(10, function(n){print(n)});
nP(10,
  function(n) {
    for (var i = 0; i < n; ++i)
      print('a');
  }
);

Block and Method

编辑

定義方法時,若參數名稱前冠上 & 符號, Ruby 將自動轉換程式碼區塊為 Proc 實例(隱性調用 Proc.new),令其成為具名參數。

def nP(n, &p)
    p.call n
end

def mP(m, p)
    p.call m
end

nP('john') {|name| puts name}

mP('bob', Proc.new {|name| puts name} )
mP('bob', proc {|name| puts name} )  # Kernel::proc

大多數情形,我們只需要傳遞一段程式 (一個程式碼區塊) ,所以 Ruby 提供了自動轉換程式碼區塊為 Proc 實例的機制。作為待轉換為具名參數的程式碼區塊引數,必須位於方法定義之參數列的最後一個。

如果要傳遞多段程式,則不適用上述轉換機制。程序員必須明確指示處理動作。

def xP(n, &p1, &p2)  # ERROR!
end

def xP(n, p1, p2)
    p1.call n
    p2.call n
end

xP(5,
    proc {|n| puts n },
    proc {|n| n.times {puts 'a'} }
)

通常應用於動態繫結的場合。

class DP
    def initialize(&p)
        @do = p
    end
    def do(n)
        @do.call n
    end
end

d = DP.new {|n| puts n}
d.do 5

The ampersand (&)

编辑

Another (IMHO far more efficacious) use of the ampersand is the other-way conversion - converting a Proc into a block. This is very useful because many of Ruby’s great built-ins, and especially the iterators, expect to receive a block as an argument, and sometimes it’s much more convenient to pass them a Proc. The following example is taken right from the excellent “Programming Ruby” book by the pragmatic programmers:


 print "(t)imes or (p)lus: "
 times = gets
 print "number: "
 number = Integer(gets)
 if times =~ /^t/
     calc = lambda {|n| n*number }
 else
     calc = lambda {|n| n+number }
 end
 puts((1..10).collect(&calc).join(", "))

The collect method expects a block, but in this case it is very convenient to provide it with a Proc, since the Proc is constructed using knowledge gained from the user. The ampersand preceding calc makes sure that the Proc object calc is turned into a code block and is passed to collect as an attached block.

The ampersand also allows the implementation of a very common idiom among Ruby programmers: passing method names into iterators. Assume that I want to convert all words in an Array to upper case. I could do it like this:


 words = %w(Jane, aara, multiko)
 upcase_words = words.map {|x| x.upcase}
 
 p upcase_words

This is nice, and it works, but I feel it’s a little bit too verbose. The upcase method itself should be given to map, without the need for a separate block and the apparently superfluous x argument. Fortunately, as we saw before, Ruby supports the idiom of sending messages to objects, and methods can be referred to by their names, which are implemented as Ruby Symbols. For example:


 p "Erik".send(:upcase)

This, quite literally, says send the message/method upcase to the object “Erik”. This feature can be utilized to implement the map {|x| x.upcase} in an elegant manner, and we’re going to use the ampersand for this ! As I said, when the ampersand is prepended to some Proc in a method call, it converts the Proc to a block. But what if we prepend it not to a Proc, but to another object ? Then, Ruby’s implicit type conversion rules kick in, and the to_proc method is called on the object to try and make a Proc out of it. We can use this to implement to_proc for Symbol and achieve what we want:


 class Symbol
     
     # A generalized conversion of a method name
     # to a proc that runs this method.
     #
     def to_proc
         lambda {|x, *args| x.send(self, *args)}
     end
     
 end
 
 # Voila !
 words = %w(Jane, aara, multiko)
 upcase_words = words.map(&:upcase)

迭代器(Iterator)

编辑

迭代作動詞時指: 反覆、重複,對應英文 iterate ;作名詞時對應英文 iteration 。根據前述字源,迭代器意為一種會反覆做相同工作的事物,對應英文 iterator 。在 Ruby 中,每個迭代器都是一種個體的方法。

語法:

個體.迭代器 do |參數|
   ...程式區塊...
end
個體.送代器 { |參數|
   ...程式區塊...
}

範例:

5.times do |c|
   puts c
end

如上例,times 是一個迭代器(iterator),會做 5 次迭代(iteration)。

撰寫程式碼時,常有各式各樣的情形需要循環處理。在 C 語言中,我們用 forwhile。例如:

for (str = "abcdefg"; *str != '\0'; str++) {
    /* 處理動作 */
}

C 語言的 for(...) 語法幫助我們建立抽象的迴圈動作,但仍要求程序員了解字串內部結構的細節,才能寫出測試 *str 是否為 null 字元的程式碼。這使得 C 語言感覺像是低階語言。高階語言應提供更有彈性的迭代。看看下列的 sh 指令手稿的做法:

#!/bin/sh
for i in *.[ch]; do
    # ... 針對每一個檔案做某件事
done

目前目錄下所有 C 語言源碼及標頭檔案都會被處理,由命令外層負責一一取得檔案名稱。這種作法看來比 C 語言高階多了,不是嗎?

但還有需要考量之事: 程式語言能否為我們自定的資料型態提供如同內建資料型態一般的迭代方式?個體導向編程 (OOP) 時,我們時常定義一個又一個的資料型態,所以須認真對待這問題。

為達上述目的,每種個體導向程式語言各有一些方便進行迭代的能力。有些語言為此目的提供了特殊的類別; Ruby 則允許我們直接定義迭代內容。

Ruby 的 String(字串) 型態有些實用的迭代器:

irb> "xyz".each_byte{ |c|
irb*     printf "<%c>", c
irb> }; puts "\n"
<x><y><z>
=> nil

each_byte 是處理字串中每一個字元的迭代器。每個字元都會被代入參數 c 。若以類似 C 語言的程式碼來表達,可改寫成...

irb> s = "xyz"; i = 0
=> 0
irb> while i < s.length
irb>     printf "<%c>", s[i]
irb>     i+=1
irb> end; puts "\n"
<x><y><z>
=> nil

... 然而, each_byte 迭代器的作法較簡單,即使未來大幅改變 String 類別的結構,通常仍可繼續運作。迭代的其中一項好處在其有助於對此類變化設計耐用的程式碼;一般而言確實是良好的程式編寫特性。

另一個有用的字串迭代器是 each_line

irb> "a\nb\nc\n".each_line{ |l|
irb*     print l
irb> }
a
b
c
=> "a\nb\nc\n"

用迭代器比在 C 語言中做同樣的事要容易得多。

事實上,Ruby 的 for 敘述就是使用調用個體的 each 迭代器進行反覆作業。字串的 eacheach_line 的效果相同,所以上述範例可以 for 改寫為:

irb> for l in "a\nb\nc\n"
irb>     print l
irb> end
a
b
c
=> "a\nb\nc\n"

我們可以在迴圈中結合 retry 敘述令迴圈重新開始工作。

irb> c = 0
=> 0
irb> for i in 0..4
irb>     print i
irb>     if i == 2 and c == 0
irb>         c = 1
irb>         print "\n"
irb>         retry
irb>     end
irb> end; print "\n"
012
01234
=> nil

上述迴圈原本應該只執行 5 次迭代,印出 01234 。但途中做了一次 retry ,故令迴圈重新自初始狀態 (i 為 0) 時開始工作。將上述範例中的 retry 換成 redo ,只會令迴圈再做一次現行迭代,其輸出內容為:

012
234

此時迴圈並未重新開始,僅反覆一次 i 為 2 時的工作。

我們可將一個程式碼區塊傳遞給迭代器,並於迭代器中以 yield 敘述將程式流程轉移至該程式碼區塊。下列範例定義了名為 repeat 的迭代器,它會依據引數所指定之次數,反覆執行程式碼區塊內容。

irb> def repeat(num)
irb>     while num > 0
irb>         yield
irb>         num -= 1
irb>     end
irb> end
=> nil
irb> repeat(3) { puts "foo" }
foo
foo
foo
=> nil

利用 retry ,我們可定義一個擬似 Ruby 標準 while 敘述作用的迭代器。

irb> def WHILE(cond)
irb>     return if not cond
irb>     yield
irb>     retry
irb> end
=> nil
irb> i = 0; WHILE(i < 3) { print i; i+=1 }
012=> nil
上一項: 控制結構 索引 下一項: 類別