Bisakah Anda memberikan argumen ke sintaks map (&: method) di Ruby?

116

Anda mungkin sudah familiar dengan singkatan Ruby berikut ( ais an array):

a.map(&:method)

Misalnya, coba yang berikut ini di irb:

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

Sintaksnya a.map(&:class)adalah singkatan dari a.map {|x| x.class}.

Baca lebih lanjut tentang sintaks ini di " Apa arti map (&: name) di Ruby? ".

Melalui sintaks &:class, Anda membuat panggilan metode classuntuk setiap elemen array.

Pertanyaan saya adalah: dapatkah Anda memberikan argumen ke pemanggilan metode? Dan jika ya, bagaimana caranya?

Misalnya, bagaimana Anda mengonversi sintaks berikut

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

ke &:sintaks?

Saya tidak menyarankan bahwa &:sintaksnya lebih baik. Saya hanya tertarik pada mekanisme penggunaan &:sintaks dengan argumen.

Saya berasumsi Anda tahu itu +adalah metode pada kelas Integer. Anda dapat mencoba yang berikut di irb:

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

Jawaban:

139

Anda dapat membuat tambalan sederhana Symbolseperti ini:

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

Yang akan memungkinkan Anda melakukan tidak hanya ini:

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

Tetapi juga banyak hal keren lainnya, seperti meneruskan beberapa parameter:

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] 

Dan bahkan bekerja dengan inject, yang meneruskan dua argumen ke blok:

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

Atau sesuatu yang sangat keren seperti meneruskan blok [singkatan] ke blok singkatan:

[['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]] 

Berikut adalah percakapan saya dengan @ArupRakshit yang menjelaskannya lebih lanjut:
Dapatkah Anda memberikan argumen ke sintaks map (&: method) di Ruby?


Seperti yang disarankan @amcaplan pada komentar di bawah , Anda dapat membuat sintaks yang lebih pendek, jika Anda mengganti nama withmetode menjadi call. Dalam hal ini, ruby ​​memiliki pintasan bawaan untuk metode khusus ini .().

Jadi Anda bisa menggunakan yang di atas seperti ini:

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]] 
Uri Agassi
sumber
5
Hebat, semoga ini adalah bagian dari inti Ruby!
Jikku Jose
6
@UriAgassi Hanya karena banyak perpustakaan melakukan ini tidak menjadikannya praktik yang baik. Meskipun Symbol#withmungkin tidak ada di pustaka inti dan mendefinisikan metode itu kurang merusak daripada mendefinisikan ulang metode yang sudah ada, itu masih mengubah (yaitu menimpa) implementasi kelas inti pustaka ruby. Latihan ini harus dilakukan dengan sangat hati-hati dan hati-hati. \ n \ n Harap pertimbangkan untuk mewarisi dari kelas yang sudah ada dan memodifikasi kelas yang baru dibuat. Ini umumnya mencapai hasil yang sebanding tanpa efek samping negatif dari perubahan kelas inti ruby.
rudolph9
2
@ rudolph9 - Saya mohon untuk berbeda - definisi "menimpa" adalah menulis lebih sesuatu, yang berarti bahwa kode yang ditulis tidak lagi tersedia, dan ini jelas tidak terjadi. Mengenai saran Anda untuk mewarisi Symbolkelas - itu tidak sepele (jika mungkin), karena ini adalah kelas inti (tidak memiliki newmetode, misalnya), dan penggunaannya akan rumit (jika mungkin), yang akan mengalahkan tujuan peningkatan ... jika Anda dapat menunjukkan implementasi yang menggunakan itu, dan mencapai hasil yang sebanding - silakan bagikan!
Uri Agassi
3
Saya suka solusi ini, tapi saya pikir Anda bisa lebih bersenang-senang dengannya. Daripada menentukan withmetode, definisikan call. Kemudian Anda dapat melakukan hal-hal seperti a.map(&:+.(2))sejak object.()menggunakan #callmetode tersebut. Dan saat Anda melakukannya, Anda dapat menulis hal-hal menyenangkan seperti :+.(2).(3) #=> 5- terasa seperti LISPy, bukan?
amcaplan
2
Ingin sekali melihat ini pada intinya - ini adalah pola umum yang dapat menggunakan sedikit gula ala .map (&: foo)
Stephen
48

Untuk contoh Anda bisa dilakukan 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)> 

Inilah cara kerjanya: -

[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(:+)memberikan sebuah Methodobjek. Kemudian &, pada 2.method(:+), sebenarnya #to_procmetode panggilan , yang membuatnya menjadi Procobjek. Kemudian ikuti Apa yang Anda sebut operator &: di Ruby? .

Arup Rakshit
sumber
Penggunaan yang cerdas! Apakah itu mengasumsikan bahwa pemanggilan metode dapat diterapkan dua cara (yaitu arr [elemen] .method (param) === param.method (arr [elemen])) atau apakah saya bingung?
Kostas Rousis
@rkon saya tidak mendapatkan pertanyaan Anda juga. Tetapi jika Anda melihat Pryoutput di atas, Anda bisa mendapatkannya, bagaimana cara kerjanya.
Arup Rakshit
5
@rkon Ini tidak bekerja dalam kedua cara. Ini berfungsi dalam kasus khusus ini karena +bersifat komutatif.
sawa
Bagaimana Anda bisa memberikan banyak argumen? Seperti dalam kasus ini: a.map {| x | x.method (1,2,3)}
Zack Xu
1
itulah maksud saya @sawa :) Itu masuk akal dengan + tetapi tidak untuk metode lain atau katakanlah jika Anda ingin membagi setiap angka dengan X.
Kostas Rousis
11

Seperti yang dikonfirmasi oleh postingan yang Anda tautkan, a.map(&:class)ini bukan singkatan a.map {|x| x.class}tapi untuk a.map(&:class.to_proc).

Ini berarti to_procdipanggil apapun yang mengikuti &operator.

Jadi Anda bisa memberikannya secara langsung sebagai Procgantinya:

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

Saya tahu bahwa kemungkinan besar ini mengalahkan tujuan pertanyaan Anda, tetapi saya tidak dapat melihat jalan lain - bukan karena Anda menentukan metode mana yang akan dipanggil, Anda hanya meneruskan sesuatu yang merespons to_proc.

Kostas Rousis
sumber
1
Ingat juga bahwa Anda dapat mengatur procs ke variabel lokal dan meneruskannya ke peta. my_proc = Proc.new{|i| i + 1},[1,2,3,4].map(&my_proc) => [2,3,4,5]
rudolph9
10

Jawaban singkatnya: Tidak.

Mengikuti jawaban @ rkon, Anda juga bisa melakukan ini:

a = [1,3,5,7,9]
a.map &->(_) { _ + 2 } # => [3, 5, 7, 9, 11]
Agis
sumber
9
Anda benar, tapi menurut saya tidak &->(_){_ + 2}ada yang lebih pendek dari {|x| x + 2}.
sawa
1
Bukan, itulah yang dikatakan @rkon dalam jawabannya jadi saya tidak mengulanginya.
Agis
2
@Agis meskipun jawaban Anda tidak lebih pendek, itu terlihat lebih baik.
Jikku Jose
1
Itu adalah solusi yang luar biasa.
BenMorganIO
5

Alih-alih menambal kelas inti sendiri, seperti dalam jawaban yang diterima, lebih pendek dan lebih bersih untuk menggunakan fungsionalitas permata Facets :

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

Ada opsi asli lain untuk enumerables yang cukup hanya untuk dua argumen menurut saya. kelas Enumerablememiliki metode with_objectyang kemudian mengembalikan yang lain Enumerable.

Jadi Anda bisa memanggil &operator untuk metode dengan setiap item dan objek sebagai argumen.

Contoh:

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

Jika Anda menginginkan lebih banyak argumen, Anda harus mengulangi prosesnya tetapi menurut saya itu jelek:

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

Saya tidak yakin tentang yang Symbol#withsudah diposting, saya menyederhanakannya sedikit dan berfungsi dengan baik:

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

(juga menggunakan public_sendbukan senduntuk mencegah memanggil metode pribadi, juga callersudah digunakan oleh ruby jadi ini membingungkan)

localhostdotdev
sumber