Potete fornire argomenti alla sintassi map (&: method) in Ruby?


116

Probabilmente hai familiarità con la seguente scorciatoia di Ruby ( aè un array):

a.map(&:method)

Ad esempio, prova quanto segue in irb:

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

La sintassi a.map(&:class)è un'abbreviazione per a.map {|x| x.class}.

Maggiori informazioni su questa sintassi in " Cosa significa map (&: name) in Ruby? ".

Attraverso la sintassi &:class, stai effettuando una chiamata al metodo classper ogni elemento dell'array.

La mia domanda è: puoi fornire argomenti alla chiamata al metodo? E se sì, come?

Ad esempio, come convertire la seguente sintassi

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

alla &:sintassi?

Non sto suggerendo che la &:sintassi sia migliore. Sono semplicemente interessato alla meccanica dell'uso della &:sintassi con gli argomenti.

Presumo che tu sappia che +è un metodo sulla classe Integer. Puoi provare quanto segue in irb:

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

Risposte:


139

Puoi creare una semplice patch Symbolcome questa:

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

Il che ti consentirà di fare non solo questo:

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

Ma anche molte altre cose interessanti, come il passaggio di più parametri:

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] 

E anche lavorare con inject, che passa due argomenti al blocco:

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

O qualcosa di molto interessante come passare blocchi [stenografici] al blocco stenografico:

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

Ecco una conversazione che ho avuto con @ArupRakshit spiegandola ulteriormente:
puoi fornire argomenti alla sintassi della mappa (&: metodo) in Ruby?


Come suggerito da @amcaplan nel commento qui sotto , puoi creare una sintassi più breve, se rinomini il withmetodo in call. In questo caso, ruby ​​ha una scorciatoia incorporata per questo metodo speciale .().

Quindi potresti usare quanto sopra in questo modo:

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
Fantastico, vorrei che questo facesse parte del nucleo di Ruby!
Jikku Jose

6
@UriAgassi Solo perché molte biblioteche lo fanno non è una buona pratica. Sebbene Symbol#withpossa non esistere nella libreria principale e definire quel metodo è meno distruttivo rispetto alla ridefinizione di un metodo esistente, sta comunque cambiando (cioè sovrascrivendo) l'implementazione della classe principale della libreria ruby. La pratica dovrebbe essere eseguita con molta parsimonia e con grande cautela. \ n \ n Considerare di ereditare dalla classe esistente e modificare la classe appena creata. Questo generalmente consente di ottenere risultati comparabili senza gli effetti collaterali negativi del cambiamento delle classi principali di rubini.
rudolph9

2
@ rudolph9 - mi permetto di dissentire - la definizione di "sovrascrittura" è quello di scrivere su qualcosa, il che significa che un codice che è stato scritto non è più disponibile, e questo non è chiaramente il caso. Per quanto riguarda il tuo suggerimento di ereditare la Symbolclasse, non è banale (se anche possibile), poiché è una classe centrale (non ha newmetodo, per esempio), e il suo utilizzo sarà macchinoso (se anche possibile), il che sconfigge il scopo del miglioramento ... se riesci a mostrare un'implementazione che lo utilizza e ottiene risultati comparabili, per favore condividi!
Uri Agassi

3
Mi piace questa soluzione, ma penso che tu possa divertirti ancora di più con essa. Invece di definire un withmetodo, definisci call. Quindi puoi fare cose come a.map(&:+.(2))poiché object.()usa il #callmetodo. E già che ci sei, puoi scrivere cose divertenti come :+.(2).(3) #=> 5- ti senti una specie di LISPy, no?
amcaplan

2
Mi piacerebbe vederlo in core - è un modello comune che potrebbe usare un po 'di zucchero ala .map (&: foo)
Stephen

48

Per il tuo esempio si può fare 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)> 

Ecco come funziona :-

[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(:+)dà un Methodoggetto. Quindi &, in 2.method(:+)realtà, un #to_procmetodo di chiamata , che lo rende un Procoggetto. Quindi segui Come chiami l'operatore &: in Ruby? .


Utilizzo intelligente! Ciò presuppone che l'invocazione del metodo possa essere applicata in entrambi i modi (ad es. Arr [elemento] .method (param) === param.method (arr [elemento])) o sono confuso?
Kostas Rousis

@rkon Anche io non ho ricevuto la tua domanda. Ma se vedi le Pryuscite sopra, puoi ottenerlo, come funziona.
Arup Rakshit

5
@rkon Non funziona in entrambi i modi. Funziona in questo caso particolare perché +è commutativo.
sawa

Come puoi fornire più argomenti? Come in questo caso: a.map {| x | x.method (1,2,3)}
Zack Xu

1
questo è il mio punto @sawa :) Che ha senso con + ma non lo farebbe per un altro metodo o diciamo se si desidera dividere ogni numero per X.
Kostas Rousis

11

Come conferma il post a cui ti sei collegato, a.map(&:class)non è una scorciatoia per a.map {|x| x.class}ma per a.map(&:class.to_proc).

Ciò significa che to_procviene richiamato su ciò che segue l' &operatore.

Quindi potresti dargli direttamente un Procinvece:

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

So che molto probabilmente questo vanifica lo scopo della tua domanda, ma non riesco a vedere altro modo per aggirarlo - non è che tu specifichi quale metodo essere chiamato, gli passi semplicemente qualcosa che risponde to_proc.


1
Inoltre, tieni presente che puoi impostare proc su variabili locali e passarle a map. my_proc = Proc.new{|i| i + 1},[1,2,3,4].map(&my_proc) => [2,3,4,5]
rudolph9

10

Risposta breve: No.

Seguendo la risposta di @ rkon, potresti anche fare questo:

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

9
Hai ragione, ma non credo &->(_){_ + 2}sia più breve di {|x| x + 2}.
sawa

1
Non lo è, è quello che dice @rkon nella sua risposta, quindi non l'ho ripetuto.
Agis

2
@Agis anche se la tua risposta non è più breve, sembra migliore.
Jikku Jose

1
Questa è una soluzione fantastica.
BenMorganIO

5

Invece di patchare le classi principali da soli, come nella risposta accettata, è più breve e più pulito utilizzare la funzionalità della gemma Facets :

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

5

C'è un'altra opzione nativa per gli enumerabili che a mio parere è carina solo per due argomenti. la classe Enumerableha il metodo with_objectche poi ne restituisce un altro Enumerable.

Quindi puoi chiamare l' &operatore per un metodo con ogni elemento e l'oggetto come argomenti.

Esempio:

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

Nel caso in cui desideri più argomenti dovresti ripetere il processo ma secondo me è brutto:

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

Non sono sicuro del Symbol#withgià postato, l'ho semplificato un po 'e funziona bene:

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

(usa anche public_sendinvece di sendprevenire la chiamata di metodi privati, inoltre callerè già usato da ruby ​​quindi questo era fonte di confusione)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.