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


116

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

a.map(&:method)

Ví dụ, hãy thử những điều sau 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à cách viết tắt của a.map {|x| x.class}.

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

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

Câu hỏi của tôi là: bạn có thể cung cấp đối số cho phương thức gọi 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}

với &: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 giả sử bạn biết đó +là một phương thức trên lớp Integer. Bạn có thể thử các cách sau trong irb:

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

Câu trả lời:


139

Bạn có thể tạo một bản vá đơn giản Symbolnhư sau:

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ứ hay ho 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 điều gì đó cực kỳ thú vị như chuyển các khối [tốc ký] sang khối tốc ký:

[['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 trò chuyện tôi đã có với @ArupRakshit giải thích thêm về nó:
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 nhận xét bên dưới , bạn có thể tạo một cú pháp ngắn hơn, nếu bạn đổi tên withphương thức thành call. Trong trường hợp này, ruby ​​đã tích hợp sẵn một phím tắt 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]] 

5
Tuyệt vời, ước gì đây là một phần của lõi Ruby!
Jikku Jose

6
@UriAgassi Chỉ vì nhiều thư viện làm điều này không khiến nó trở thành một thực tiễn tốt. Mặc dù Symbol#withcó thể không tồn tại trong thư viện lõi và việc xác định phương thức đó ít phá hủy hơn so với việc xác định lại một phương thức hiện có, 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 tập luyện nên được thực hiện rất tiết kiệm và hết sức thận trọng. \ n \ n Vui lòng xem xét việc 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ả tương đương 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

2
@ rudolph9 - Tôi cầu xin khác biệt - định nghĩa về "ghi đè" là viết trên một cái gì đó, có nghĩa là một mã được viết là không còn nữa, và điều này rõ ràng là không phải như vậy. Về đề xuất của bạn để kế thừa Symbollớp - nó không phải là tầm thường (nếu thậm chí có thể), vì nó là một lớp cốt lõi ( newví dụ: nó không có phương thức) và việc sử dụng nó sẽ phức tạp (nếu có thể), điều này sẽ đánh bại mục đích của cải tiến ... nếu bạn có thể hiển thị một triển khai sử dụng điều đó và đạt được kết quả tương đương - vui lòng chia sẻ!
Uri Agassi

3
Tôi thích giải pháp này, nhưng tôi nghĩ bạn có thể vui hơn nữa với nó. Thay vì xác định một withphương thức, hãy xác định call. Sau đó, bạn có thể làm những việc như a.map(&:+.(2))kể từ khi object.()sử dụng #callphương thức. Và khi bạn đang ở đó, bạn có thể viết những điều thú vị như :+.(2).(3) #=> 5- cảm thấy giống như LISPy, không?
amcaplan

2
Rất thích nhìn thấy điều này trong cốt lõi - đó là một mô hình phổ biến có thể sử dụng một số đường .map (&: foo)
Stephen

48

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đối tượng. Sau đó &, trên 2.method(:+)thực tế là một #to_procphương thức gọi , làm cho nó trở thành một Procđối tượng. Sau đó làm theo Bạn gọi toán tử &: trong Ruby là gì? .


Cách sử dụng thông minh! Điều đó có giả định rằng lệnh gọi phương thức có thể được áp dụng theo cả hai cách (tức là arr [element] .method (param) === param.method (arr [element])) hay tôi bị nhầm lẫn?
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 nhìn thấy Prykết quả đầu ra ở trên, bạn có thể nhận được nó, nó đang hoạt động như thế nào.
Arup Rakshit

5
@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 vì +có tính chất 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

1
đó là quan điểm của tôi @sawa :) Điều đó có ý nghĩa với + nhưng sẽ không phù hợp với một phương pháp khác hoặc giả sử bạn muốn chia mỗi số cho X.
Kostas Rousis

11

Khi bài đăng bạn liên kết để xác nhận, a.map(&:class)không phải là cách viết tắt a.map {|x| x.class}mà cho a.map(&:class.to_proc).

Điều này có nghĩa là nó to_procđược gọi trên bất cứ thứ gì theo sau &toán tử.

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

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âu hỏi của bạn nhưng tôi không thể thấy bất kỳ cách nào khác xung quanh nó - không phải là bạn chỉ định phương thức nào sẽ được gọi, bạn chỉ chuyển nó một cái gì đó phản hồi to_proc.


1
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

10

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]

9
Bạn đúng, nhưng tôi không nghĩ &->(_){_ + 2}là ngắn hơn {|x| x + 2}.
sawa

1
Đó không phải là những gì @rkon nói trong câu trả lời của anh ấy nên tôi không lặp lại.
Agis

2
@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

1
Đó là một giải pháp tuyệt vời.
BenMorganIO,

5

Thay vì tự vá các lớp lõi, như trong câu trả lời được chấp nhận, việc sử dụng chức năng của đá quý Facets sẽ ngắn hơn và gọn gàng hơn :

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

5

Theo ý kiến ​​của tôi, có một tùy chọn gốc khác cho bảng liệt kê mà chỉ phù hợp với hai đối số. lớp Enumerablecó phương thức with_objectsau đó trả về một phương thức khác Enumerable.

Vì vậy, bạn có thể gọi &toán tử cho một phương thức với mỗi mục và đối tượng là các đối số.

Thí dụ:

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

Trong trường hợp bạn muốn có nhiều đối số hơn, bạn nên lặp lại quy trình nhưng theo tôi thì nó xấu:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+).to_enum.with_object(5).map(&:+) # => [8, 10, 12, 14, 16]

0

Tôi không chắc về những gì Symbol#withđã được đăng, tôi đã đơn giản hóa nó một chút và nó hoạt động tốt:

class Symbol
  def with(*args, &block)
    lambda { |object| object.public_send(self, *args, &block) }
  end
end

(cũng sử dụng public_sendthay vì sendđể ngăn việc gọi các phương thức riêng tư, cũng callerđã được sử dụng bởi ruby ​​nên điều này gây nhầm lẫn)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.