Ruby'de harita (&: method) sözdizimine argümanlar sağlayabilir misiniz?


116

Muhtemelen aşağıdaki Ruby kısaltmasına aşinasınız ( abir dizidir):

a.map(&:method)

Örneğin, irb'de şunu deneyin:

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

Sözdizimi a.map(&:class), için bir kısaltmadır a.map {|x| x.class}.

Bu sözdizimi hakkında daha fazlasını " harita (&: ad) Ruby'de ne anlama geliyor? " Bölümünde okuyun .

Sözdizimi aracılığıyla, her dizi öğesi için &:classbir yöntem çağrısı yaparsınız class.

Sorum şu: yöntem çağrısına argümanlar sağlayabilir misiniz? Ve eğer öyleyse, nasıl?

Örneğin, aşağıdaki sözdizimini nasıl dönüştürürsünüz?

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

için &:sözdizimi?

Ben demiyorum &:sözdizimi iyidir. Yalnızca &:sözdizimini argümanlarla kullanmanın mekaniğiyle ilgileniyorum .

Bunun +Integer sınıfında bir yöntem olduğunu bildiğinizi varsayıyorum . Aşağıdakileri irb'de deneyebilirsiniz:

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

Yanıtlar:


139

Bunun Symbolgibi basit bir yama oluşturabilirsiniz :

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

Bu sadece bunu yapmanıza olanak sağlamaz:

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

Ancak, birden çok parametreyi iletmek gibi diğer pek çok harika şey:

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] 

Ve hatta injectbloğa iki argüman ileten ile çalışın :

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

Veya [steno] blokları stenografi bloğuna geçirmek kadar harika bir şey :

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

İşte @ArupRakshit ile bunu daha ayrıntılı açıklayan bir konuşma
: Ruby'de harita (&: method) sözdizimi için argümanlar sağlayabilir misiniz?


@ Amcaplan'ın aşağıdaki yorumda önerdiği gibi , withyöntemi olarak yeniden adlandırırsanız daha kısa bir sözdizimi oluşturabilirsiniz call. Bu durumda, Ruby'nin bu özel yöntem için yerleşik bir kısayolu vardır .().

Yani yukarıdakileri şu şekilde kullanabilirsiniz:

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
Harika, keşke bu Ruby çekirdeğinin bir parçası olsaydı!
Jikku Jose

6
@UriAgassi Birçok kütüphane bunu yapıyor diye bunu iyi bir uygulama yapmaz. Birlikte Symbol#withçekirdek kütüphane bulunur ve bu yöntemi tanımlayan hala değişiyor mevcut bir yöntem (diğer bir deyişle üzerine yazma) yakut kütüphanesinin çekirdek sınıf uygulama yeniden tanımlanması daha az tahrip edici olabilir. Uygulama çok idareli ve büyük bir dikkatle yapılmalıdır. \ n \ n Lütfen mevcut sınıftan miras almayı ve yeni oluşturulan sınıfı değiştirmeyi düşünün. Bu genellikle değişen çekirdek yakut sınıflarının olumsuz yan etkileri olmadan karşılaştırılabilir sonuçlar elde eder.
rudolph9

2
@ rudolph9 - Ben yalvarıyorum farklı olmasına - "üzerine yazma" tanımı yazmaktır üzerinde yazılan bir kod mevcut değildir artık demekse şey, ve bu açıkça böyle değil. SymbolSınıfı miras alma öneriniz ile ilgili olarak - bu, çok önemli bir sınıf olduğu için (mümkünse bile) önemsiz değildir ( newörneğin, yöntemi yoktur ) ve kullanımı hantal olacaktır (mümkünse), geliştirmenin amacı ... bunu kullanan ve karşılaştırılabilir sonuçlar elde eden bir uygulama gösterebilirseniz, lütfen paylaşın!
Uri Agassi

3
Bu çözümü beğendim, ama onunla daha da eğlenebileceğinizi düşünüyorum. Bir withyöntem tanımlamak yerine tanımlayın call. Sonra gibi şeyler yapabilirsiniz a.map(&:+.(2))beri object.()kullandığı #callyöntem. Ve hazır oradayken, şöyle eğlenceli şeyler yazabilirsiniz :+.(2).(3) #=> 5- bir çeşit LISPy gibi, değil mi?
amcaplan

2
Bunu özünde görmek isterim - bu biraz şeker ala .map (&: foo) kullanabilen ortak bir model
Stephen

48

Örneğiniz için yapılabilir 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)> 

Şu şekilde çalışır: -

[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(:+)bir Methodnesne verir . Sonra &, 2.method(:+)aslında #to_proconu bir Procnesne yapan bir çağrı yöntemi . Daha sonra Ruby'de &: operatörüne ne diyorsunuz? .


Akıllı kullanım! Bu, yöntem çağrısının her iki şekilde de uygulanabileceğini varsayıyor mu (yani arr [element] .method (param) === param.method (arr [element])) yoksa kafam karıştı mı?
Kostas Rousis

@rkon Ben de sorunuzu almadım. Ama Pryyukarıdaki çıktıları görürseniz , nasıl çalıştığını anlayabilirsiniz.
Arup Rakshit

5
@rkon Her iki şekilde de çalışmıyor. Bu özel durumda işe yarıyor çünkü +değişmeli.
sawa

Nasıl birden fazla argüman sağlayabilirsiniz? Bu durumda olduğu gibi: a.map {| x | x.method (1,2,3)}
Zack Xu

1
benim amacım bu @sawa :) + ile mantıklı ama başka bir yöntem için değil ya da her sayıyı X'e bölmek istiyorsanız diyelim.
Kostas Rousis

11

Bağlandığınız gönderi onayladığı gibi a.map(&:class), bunun için bir kısaltma değil a.map {|x| x.class}ama için a.map(&:class.to_proc).

Bu to_proc, &operatörü izleyen ne olursa olsun çağrıldığı anlamına gelir .

Böylece doğrudan Procyerine verebilirsiniz :

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

Muhtemelen bunun sorunuzun amacını bozduğunu biliyorum, ancak bunun başka bir yolunu göremiyorum - bu, hangi yöntemin çağrılacağını belirlemeniz değil, sadece yanıt veren bir şeyi iletirsiniz to_proc.


1
Ayrıca, proc'ları yerel değişkenlere ayarlayabileceğinizi ve bunları haritaya aktarabileceğinizi unutmayın. my_proc = Proc.new{|i| i + 1},[1,2,3,4].map(&my_proc) => [2,3,4,5]
rudolph9

10

Kısa cevap: Hayır.

@ Rkon'un cevabını takiben, bunu da yapabilirsiniz:

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

9
Haklısın, ama &->(_){_ + 2}daha kısa olduğunu sanmıyorum {|x| x + 2}.
sawa

1
Öyle değil, @rkon cevabında böyle söylediği için tekrar etmedim.
Agis

2
@Agis cevabınız daha kısa olmasa da daha iyi görünüyor.
Jikku Jose

1
Bu harika bir çözüm.
BenMorganIO

6

Bence sadece iki argüman için güzel olan numaralandırılabilir başka bir yerel seçenek var. sınıf Enumerable, with_objectdaha sonra başka bir döndüren yönteme sahiptir Enumerable.

Böylece &, her öğe ve nesnenin bağımsız değişken olduğu bir yöntem için operatörü çağırabilirsiniz .

Misal:

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

Daha fazla argüman istiyorsanız, süreci tekrarlamalısınız ama bence bu çirkin:

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

5

Kabul edilen yanıtta olduğu gibi çekirdek sınıflarını kendiniz yamamak yerine, Facets geminin işlevselliğini kullanmak daha kısa ve daha temizdir :

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

0

Symbol#withGönderilenlerden emin değilim , biraz basitleştirdim ve iyi çalışıyor:

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

(ayrıca özel yöntemlerin çağrılmasını önlemek public_sendyerine kullanır send, ayrıca callerRuby tarafından zaten kullanılıyor, bu yüzden bu kafa karıştırıcıydı)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.