使用 lambda


程式區塊與 Proc 中談過,Proc物件像是個程式流程片段,有時候,你希望的是程式區塊更像是個方法呼叫,這時可以使用lambda方法。例如:
>> def some(lda)
>>     puts "some 1"
>>     lda.call
>>     puts "some 2"
>> end
=> nil
>> lda = lambda { puts "執行 lambda"; return 1 }
=> #<Proc:0x2161320@(irb):38 (lambda)>
>> some(lda)
some 1
執行 lambda
some 2
=> nil
>>


上例就像是:
def some
    def lda
        puts "執行 lambda"
        return 1
    end
    puts "some 1"
    lda
    puts "some 2"
end

然而你仔細觀察,使用lambda方法傳回的物件,其實也是Proc實例:
>> lda = lambda { puts "執行 lambda"; return 1 }
=> #<Proc:0x2161320@(irb):38 (lambda)>

lambda與 直接使用Proc.new建立的物件不同的地方在於,Proc.new直接建立的物件像個程式流程,而lambda方法建立的Proc物件像是個方法,因 此lambda建立時的程式區塊return,就像是個方法return,另外,lambda建立的Proc物件,有哪些區塊參數,call就只能傳入幾 個引數。例如:
>> l = lambda { |a, b| puts a, b }
=> #<Proc:0x27fffa8@(irb):17 (lambda)>
>> l.call(1, 2)

1
2
=> nil
>> l.call(1)

ArgumentError: wrong number of arguments (1 for 2)
        from (irb):17:in `block in irb_binding'
        from (irb):19:in `call'
        from (irb):19
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> l.call(1, 2, 3)

ArgumentError: wrong number of arguments (3 for 2)
        from (irb):17:in `block in irb_binding'
        from (irb):20:in `call'
        from (irb):20
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>


因此lambda建立的Proc物件,在概念上就類似其它程式語言的一級函式物件,因此在Ruby 1.9之後,直接使用語法支援lambda方法建立的Proc物件。例如:
>> l = ->(a, b) { puts a, b }
=> #<Proc:0x2468880@(irb):21 (lambda)>
>> l.call(10, 20)
10
20
=> nil
>>


圓括號中可以指定參數,若不需要參數,則圓括號可以省略。由於lambda建立的也是Proc物件,所以適用程式區塊的方法,也可以使用lambda建立的Proc物件。例如:
>> [1, 2, 3].each(&->(element) { puts element })
1
2
3
=> [1, 2, 3]
>>

不過這並不是lambda最主要的用途,由於lambda建立的Proc物件更像是個方法,所以你可以將之自由傳遞,也可以從方法中返回,而不用擔心return的問題。一個例子像是 因 式分解,可以先準備好一定長度的質數表,之後利用該質數表來進行因式分解。例如:
# encoding: Big5
class Range
def comprehend(&block)
return self if block.nil?
self.collect(&block).compact
end
end

def prepare_factor(max)
prime = Array.new(max, 1)
2.upto(Math.sqrt(max).to_i - 1) do |i|
if prime[i] == 1
(2 * i).upto(max - 1) do |j|
if j % i == 0
prime[j] = 0
end
end
end
end

primes = (2..max - 1).comprehend { |i| i if prime[i] == 1} # 質數表

->(num) {
list = []
i = 0
while primes[i] ** 2 <= num
if num % primes[i] == 0
list << primes[i]
num /= primes[i]
else
i += 1
end
end
list << num
f = Array.new(list.length, 0)
f.length.times { |i|
f[i] = list[i]
}
return f # 在這邊return是可以不寫的,只是為了強調
}
end

factor = prepare_factor(1000)
p factor.call(100) # 顯示 [2, 2, 5, 5]
p factor.call(500) # 顯示 [2, 2, 5, 5, 5]
p factor.call(800) # 顯示 [2, 2, 2, 2, 2, 5, 5]

當然,如果就這個例子,使用Proc.new直接建立粗體字部份,也可以解決問題:
def prepare_factor(max)
    ...
    Proc.new { |num|
        list = []
        i = 0
        while primes[i] ** 2 <= num
            if num % primes[i] == 0
                list << primes[i]
                num /= primes[i]
            else
                i += 1
            end
        end
        list << num
        f = Array.new(list.length, 0)
        f.length.times { |i|
            f[i] = list[i]
        }
        f
    }
end


對於一開始就使用Ruby的開發人員而言,程式區塊與Proc.new直接建立物件,確實減少了使用lambda的需求,然 而就熟悉一級函式物件的開發人員而言,對於Proc.new直接建立的物件比較像個程序而不是個方法,以及return的微妙處理方式可能不是那麼熟悉, 使用lambda對他們而言會比較容易掌握,Proc.new直接建立的物件,對他們而言會是另一個選項,多了一份彈性。