Câu hỏi Bạn có thể cung cấp các đối số cho cú pháp map (&: method) trong Ruby không?


Có lẽ bạn đã quen thuộc với cách viết tắt của Ruby (a là một mảng):

a.map(&:method)

Ví dụ, hãy thử những điều sau đây trong irb:

>> a=[:a, 'a', 1, 1.0]
=> [:a, "a", 1, 1.0]
>> a.map(&:class)
=> [Symbol, String, Fixnum, Float]

Cú pháp a.map(&:class) là viết tắt của a.map {|x| x.class}.

Đọc thêm về cú pháp này trong "Bản đồ (&: name) có ý nghĩa gì trong Ruby?".

Thông qua cú pháp &:class, bạn đang thực hiện cuộc gọi phương thức class cho mỗi phần tử mảng.

Câu hỏi của tôi là: bạn có thể cung cấp các đối số cho cuộc gọi phương thức không? Và nếu vậy, làm thế nào?

Ví dụ, làm thế nào để bạn chuyển đổi cú pháp sau

a = [1,3,5,7,9]
a.map {|x| x + 2}

đến &: cú pháp?

Tôi không gợi ý rằng &: cú pháp tốt hơn. Tôi chỉ quan tâm đến cơ chế sử dụng &: cú pháp với các đối số.

Tôi cho rằng bạn biết rằng + là một phương thức trên lớp Integer. Bạn có thể thử những điều sau đây trong irb:

>> a=1
=> 1
>> a+(1)
=> 2
>> a.send(:+, 1)
=> 2

76
2018-05-16 13:01


gốc




Các câu trả lời:


Bạn có thể tạo một bản vá đơn giản trên Symbol như thế này:

class Symbol
  def with(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

Điều này sẽ cho phép bạn không chỉ làm điều này:

a = [1,3,5,7,9]
a.map(&:+.with(2))
# => [3, 5, 7, 9, 11] 

Nhưng cũng có rất nhiều thứ thú vị khác, như truyền nhiều tham số:

arr = ["abc", "babc", "great", "fruit"]
arr.map(&:center.with(20, '*'))
# => ["********abc*********", "********babc********", "*******great********", "*******fruit********"]
arr.map(&:[].with(1, 3))
# => ["bc", "abc", "rea", "rui"]
arr.map(&:[].with(/a(.*)/))
# => ["abc", "abc", "at", nil] 
arr.map(&:[].with(/a(.*)/, 1))
# => ["bc", "bc", "t", nil] 

Và thậm chí làm việc với inject, chuyển hai đối số cho khối:

%w(abecd ab cd).inject(&:gsub.with('cde'))
# => "cdeeecde" 

Hoặc một cái gì đó siêu mát mẻ như đi qua [viết tắt] khối đến khối viết tắt:

[['0', '1'], ['2', '3']].map(&:map.with(&:to_i))
# => [[0, 1], [2, 3]]
[%w(a b), %w(c d)].map(&:inject.with(&:+))
# => ["ab", "cd"] 
[(1..5), (6..10)].map(&:map.with(&:*.with(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

Đây là cuộc hội thoại tôi đã có với @ArupRakshit giải thích thêm:
Bạn có thể cung cấp các đối số cho cú pháp map (&: method) trong Ruby không?


Như @amcaplan đề xuất trong bình luận dưới đây, bạn có thể tạo cú pháp ngắn hơn, nếu bạn đổi tên with phương pháp để call. Trong trường hợp này, ruby ​​có phím tắt tích hợp cho phương pháp đặc biệt này .().

Vì vậy, bạn có thể sử dụng ở trên như thế này:

class Symbol
  def call(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11] 

[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

110
2018-05-17 12:55



Rất thú vị, Uri! - Cary Swoveland
Rất tuyệt, đây là câu trả lời hay nhất cho câu hỏi này. - Daniël Knippers
Tuyệt vời, ước gì đây là một phần cốt lõi của Ruby! - Jikku Jose
@UriAgassi Chỉ vì rất nhiều thư viện làm điều này không làm cho nó trở thành một thực hành tốt. Trong khi Symbol#with có thể không tồn tại trong thư viện lõi và xác định phương pháp đó ít phá hoại hơn việc xác định lại một phương thức hiện có mà nó vẫn đang thay đổi (tức là ghi đè) việc triển khai lớp lõi của thư viện ruby. Việc thực hành nên được thực hiện rất ít và thận trọng. \ n \ n Vui lòng xem xét kế thừa từ lớp hiện có và sửa đổi lớp mới được tạo. Điều này thường đạt được kết quả so sánh mà không có tác dụng phụ tiêu cực của việc thay đổi các lớp ruby ​​lõi. - rudolph9
Tôi thích giải pháp này, nhưng tôi nghĩ bạn có thể vui hơn với nó. Thay vì xác định with phương pháp, xác định call. Sau đó, bạn có thể làm những việc như a.map(&:+.(2)) kể từ đó object.() sử dụng #call phương pháp. Và trong khi bạn đang ở đó, bạn có thể viết những điều thú vị như :+.(2).(3) #=> 5 - cảm thấy loại LISPy, phải không? - amcaplan


Ví dụ của bạn có thể được thực hiện a.map(&2.method(:+)).

Arup-iMac:$ pry
[1] pry(main)> a = [1,3,5,7,9]
=> [1, 3, 5, 7, 9]
[2] pry(main)> a.map(&2.method(:+))
=> [3, 5, 7, 9, 11]
[3] pry(main)> 

Đây là cách nó làm việc :-

[3] pry(main)> 2.method(:+)
=> #<Method: Fixnum#+>
[4] pry(main)> 2.method(:+).to_proc
=> #<Proc:0x000001030cb990 (lambda)>
[5] pry(main)> 2.method(:+).to_proc.call(1)
=> 3

2.method(:+) đưa ra một Method vật. Sau đó &, trên 2.method(:+), thực sự là một cuộc gọi #to_proc phương pháp, làm cho nó trở thành Proc vật. Sau đó làm theo Bạn gọi toán tử &: trong Ruby là gì?.


36
2018-05-16 13:17



Sử dụng thông minh! Điều đó giả định rằng lời gọi phương thức có thể được áp dụng cả hai cách (ví dụ: arr [element] .method (param) === param.method (arr [element])) hoặc am I confused? - Kostas Rousis
@rkon Tôi cũng không nhận được câu hỏi của bạn. Nhưng nếu bạn thấy Pry kết quả đầu ra ở trên, bạn có thể lấy nó, nó hoạt động như thế nào. - Arup Rakshit
@rkon Nó không hoạt động theo cả hai cách. Nó hoạt động trong trường hợp cụ thể này bởi vì + là giao hoán. - sawa
Làm thế nào bạn có thể cung cấp nhiều đối số? Như trong trường hợp này: a.map {| x | x.method (1,2,3)} - Zack Xu
đó là điểm của tôi @sawa :) Điều đó có ý nghĩa với + nhưng sẽ không cho một phương pháp khác hoặc giả sử nếu bạn muốn chia từng số bằng X. - Kostas Rousis


Là bài đăng bạn đã liên kết để xác nhận, a.map(&:class) không phải là viết tắt của a.map {|x| x.class} nhưng đối với a.map(&:class.to_proc).

Điều này có nghĩa rằng to_proc được gọi là bất cứ điều gì theo sau & nhà điều hành.

Vì vậy, bạn có thể cung cấp trực tiếp Proc thay thế:

a.map(&(Proc.new {|x| x+2}))

Tôi biết rằng hầu hết điều này có thể đánh bại mục đích của câu hỏi của bạn nhưng tôi không thể nhìn thấy bất kỳ cách nào khác xung quanh nó - nó không phải là bạn chỉ định phương pháp để được gọi, bạn chỉ cần vượt qua nó một cái gì đó mà phản ứng to_proc.


9
2018-05-16 13:13



Cũng nên nhớ rằng bạn có thể đặt procs thành các biến cục bộ và chuyển chúng vào bản đồ. my_proc = Proc.new{|i| i + 1}, [1,2,3,4].map(&my_proc) => [2,3,4,5] - rudolph9


Câu trả lời ngắn gọn: Không.

Sau câu trả lời của @ rkon, bạn cũng có thể làm điều này:

a = [1,3,5,7,9]
a.map &->(_) { _ + 2 } # => [3, 5, 7, 9, 11]

7
2018-05-16 13:41



Bạn nói đúng, nhưng tôi không nghĩ &->(_){_ + 2} ngắn hơn {|x| x + 2}. - sawa
Nó không phải là, đó là những gì @rkon nói trong câu trả lời của mình vì vậy tôi đã không lặp lại nó. - Agis
@ Mặc dù câu trả lời của bạn không ngắn hơn, nhưng có vẻ tốt hơn. - Jikku Jose
Đó là một giải pháp tuyệt vời. - BenMorganIO


Thay vì tự vá các lớp lõi, như trong câu trả lời được chấp nhận, nó ngắn hơn và sạch hơn để sử dụng chức năng của Facets gem:

require 'facets'
a = [1,3,5,7,9]
a.map &:+.(2)

4
2017-08-26 14:24