keuntungan dari metode tap di ruby

116

Saya baru saja membaca artikel blog dan memperhatikan bahwa penulis menggunakan tappotongan seperti:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

Pertanyaan saya sebenarnya apa sih manfaat atau keuntungan menggunakan tap? Tidak bisakah saya melakukan:

user = User.new
user.username = "foobar"
user.save!

atau lebih baik lagi:

user = User.create! username: "foobar"
Kyle Decot
sumber

Jawaban:

103

Saat pembaca menemukan:

user = User.new
user.username = "foobar"
user.save!

mereka harus mengikuti ketiga baris tersebut dan kemudian menyadari bahwa itu hanya membuat sebuah instance bernama user.

Jika itu adalah:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

maka itu akan segera menjadi jelas. Seorang pembaca tidak perlu membaca apa yang ada di dalam blok untuk mengetahui bahwa sebuah instance userdibuat.

sawa
sumber
3
@Matt: Dan juga, buang definisi variabel apa pun yang dibuat dalam proses setelah blok menyelesaikan tugasnya. Dan jika hanya ada satu metode yang dipanggil pada objek, Anda dapat menulisUser.new.tap &:foobar
Boris Stitnicky
28
Saya tidak menemukan penggunaan ini sangat menarik - bisa dibilang tidak lebih mudah dibaca, itulah mengapa ada di halaman ini. Tanpa argumen keterbacaan yang kuat, saya membandingkan kecepatan. Pengujian saya menunjukkan runtime tambahan 45% untuk implementasi sederhana di atas, berkurang karena jumlah setter pada objek meningkat - sekitar 10 atau lebih dari mereka dan perbedaan runtime dapat diabaikan (YMMV). 'menyadap' ke dalam rangkaian metode selama debugging tampaknya seperti kemenangan, jika tidak, saya perlu lebih banyak untuk membujuk saya.
dinman2022
7
Saya pikir sesuatu seperti user = User.create!(username: 'foobar')akan menjadi yang paling jelas dan terpendek dalam kasus ini :) - contoh terakhir dari pertanyaan tersebut.
Lee
4
Jawaban ini bertentangan dengan dirinya sendiri, dan karenanya tidak masuk akal. Lebih banyak yang terjadi daripada "hanya membuat sebuah instance bernama user." Juga, argumen bahwa "Seorang pembaca tidak perlu membaca apa yang ada di dalam blok untuk mengetahui bahwa sebuah instance userdibuat." tidak memiliki bobot, karena di blok kode pertama, pembaca juga hanya perlu membaca baris pertama "untuk mengetahui bahwa sebuah instance userdibuat".
Jackson
5
Lalu mengapa saya di sini? Mengapa kita semua di sini mencari apa itu tap.
Eddie
37

Kasus lain menggunakan tap adalah melakukan manipulasi pada objek sebelum mengembalikannya.

Jadi, alih-alih ini:

def some_method
  ...
  some_object.serialize
  some_object
end

kami dapat menghemat baris tambahan:

def some_method
  ...
  some_object.tap{ |o| o.serialize }
end

Dalam beberapa situasi, teknik ini dapat menghemat lebih dari satu baris dan membuat kode lebih kompak.

megas
sumber
24
Saya akan menjadi lebih drastis:some_object.tap(&:serialize)
amencarini
28

Menggunakan tap, seperti yang dilakukan blogger, hanyalah metode kenyamanan. Ini mungkin berlebihan dalam contoh Anda, tetapi dalam kasus di mana Anda ingin melakukan banyak hal dengan pengguna, tap bisa dibilang menyediakan antarmuka yang tampak lebih bersih. Jadi, mungkin lebih baik dalam contoh sebagai berikut:

user = User.new.tap do |u|
  u.build_profile
  u.process_credit_card
  u.ship_out_item
  u.send_email_confirmation
  u.blahblahyougetmypoint
end

Menggunakan cara di atas memudahkan untuk melihat dengan cepat bahwa semua metode tersebut dikelompokkan bersama sehingga semuanya merujuk ke objek yang sama (pengguna dalam contoh ini). Alternatifnya adalah:

user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint

Sekali lagi, ini masih bisa diperdebatkan - tetapi kasusnya dapat dibuat bahwa versi kedua terlihat sedikit lebih berantakan, dan membutuhkan sedikit lebih banyak penguraian manusia untuk melihat bahwa semua metode dipanggil pada objek yang sama.

Rebitzele
sumber
2
Ini hanyalah contoh yang lebih panjang dari apa yang OP sudah masukkan ke dalam pertanyaannya, Anda masih dapat melakukan semua hal di atas dengan user = User.new, user.do_something, user.do_another_thing... bisakah Anda menjelaskan mengapa orang melakukan ini?
Matt
Meskipun contohnya pada dasarnya sama, ketika menunjukkannya dalam bentuk yang lebih panjang, orang dapat melihat bagaimana penggunaan tap mungkin lebih menarik secara estetika untuk kasus ini. Saya akan menambahkan edit untuk membantu mendemonstrasikan.
Rebitzele
Saya juga tidak melihatnya. Menggunakan taptidak pernah menambahkan manfaat apa pun dalam pengalaman saya. Membuat, dan bekerja dengan, uservariabel lokal jauh lebih bersih, dan dapat dibaca menurut saya.
gylaz
Keduanya tidak setara. Jika Anda melakukannya u = user = User.newdan kemudian digunakan uuntuk panggilan pengaturan, itu akan lebih sesuai dengan contoh pertama.
Gerry
26

Ini bisa berguna dengan men - debug serangkaian ActiveRecordcakupan yang dirantai.

User
  .active                      .tap { |users| puts "Users so far: #{users.size}" } 
  .non_admin                   .tap { |users| puts "Users so far: #{users.size}" }
  .at_least_years_old(25)      .tap { |users| puts "Users so far: #{users.size}" }
  .residing_in('USA')

Ini membuatnya sangat mudah untuk men-debug di titik mana pun dalam rantai tanpa harus menyimpan apa pun di variabel lokal atau memerlukan banyak perubahan pada kode asli.

Dan terakhir, gunakan sebagai cara cepat dan tidak mengganggu untuk men-debug tanpa mengganggu eksekusi kode normal :

def rockwell_retro_encabulate
  provide_inverse_reactive_current
  synchronize_cardinal_graham_meters
  @result.tap(&method(:puts))
  # Will debug `@result` just before returning it.
end
Gupta
sumber
14

Visualisasikan contoh Anda dalam suatu fungsi

def make_user(name)
  user = User.new
  user.username = name
  user.save!
end

Ada risiko pemeliharaan yang besar dengan pendekatan itu, pada dasarnya nilai pengembalian implisit .

Dalam kode itu, Anda bergantung pada save!mengembalikan pengguna yang disimpan. Tetapi jika Anda menggunakan bebek yang berbeda (atau bebek Anda saat ini berkembang), Anda mungkin mendapatkan hal-hal lain seperti laporan status penyelesaian. Oleh karena itu, perubahan pada bebek mungkin merusak kode, sesuatu yang tidak akan terjadi jika Anda memastikan nilai pengembalian dengan userketukan biasa atau gunakan.

Saya telah sering melihat kecelakaan seperti ini, khususnya dengan fungsi di mana nilai pengembalian biasanya tidak digunakan kecuali untuk satu sudut kereta gelap.

Nilai pengembalian implisit cenderung menjadi salah satu hal di mana pemula cenderung merusak hal-hal dengan menambahkan kode baru setelah baris terakhir tanpa memperhatikan efeknya. Mereka tidak melihat arti sebenarnya dari kode di atas:

def make_user(name)
  user = User.new
  user.username = name
  return user.save!       # notice something different now?
end
SystematicFrank
sumber
1
Sama sekali tidak ada perbedaan antara kedua contoh Anda. Apakah Anda bermaksud untuk kembali user?
Bryan Ash
1
Itulah maksudnya: contoh-contohnya persis sama, yang satu hanya eksplisit tentang pengembaliannya. Maksudnya adalah bahwa ini dapat dihindari dengan menggunakan keran:User.new.tap{ |u| u.username = name; u.save! }
Obversity
14

Jika Anda ingin mengembalikan pengguna setelah menyetel nama pengguna, Anda harus melakukannya

user = User.new
user.username = 'foobar'
user

Dengan tapAnda bisa menyelamatkan pengembalian canggung itu

User.new.tap do |user|
  user.username = 'foobar'
end
montrealmike
sumber
1
Ini adalah kasus penggunaan paling umum Object#tapbagi saya.
Lyndsy Simon
1
Nah, Anda telah menyimpan nol baris kode, dan sekarang, ketika melihat akhir metode untuk mengembalikannya, saya harus memindai kembali untuk melihat bahwa blok tersebut adalah # blok ketuk. Tidak yakin ini adalah kemenangan apa pun.
Irongaze.com
mungkin tapi ini bisa dengan mudah menjadi 1 liner user = User.new.tap {|u| u.username = 'foobar' }
lacostenycoder
11

Ini menghasilkan kode yang tidak terlalu berantakan karena ruang lingkup variabel terbatas hanya pada bagian yang benar-benar dibutuhkan. Selain itu, lekukan di dalam blok membuat kode lebih mudah dibaca dengan menyatukan kode yang relevan.

Deskripsi tapkata :

Menghasilkan diri ke blok, dan kemudian mengembalikan diri. Tujuan utama dari metode ini adalah untuk "memanfaatkan" rantai metode, untuk melakukan operasi pada hasil antara dalam rantai tersebut.

Jika kita mencari kode sumber rails untuk tapdigunakan , kita dapat menemukan beberapa penggunaan yang menarik. Di bawah ini adalah beberapa item (bukan daftar lengkap) yang akan memberi kita sedikit ide tentang cara menggunakannya:

  1. Menambahkan elemen ke larik berdasarkan kondisi tertentu

    %w(
    annotations
    ...
    routes
    tmp
    ).tap { |arr|
      arr << 'statistics' if Rake.application.current_scope.empty?
    }.each do |task|
      ...
    end
  2. Menginisialisasi sebuah array dan mengembalikannya

    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
  3. Sebagai gula sintaksis untuk membuat kode lebih mudah dibaca - Dapat dikatakan, dalam contoh di bawah ini, penggunaan variabel hashdan servermembuat maksud kode lebih jelas.

    def select(*args, &block)
        dup.tap { |hash| hash.select!(*args, &block) }
    end
  4. Inisialisasi / panggil metode pada objek yang baru dibuat.

    Rails::Server.new.tap do |server|
       require APP_PATH
       Dir.chdir(Rails.application.root)
       server.start
    end

    Di bawah ini adalah contoh dari file tes

    @pirate = Pirate.new.tap do |pirate|
      pirate.catchphrase = "Don't call me!"
      pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
      pirate.save!
    end
  5. Untuk bertindak atas hasil yieldpanggilan tanpa harus menggunakan variabel sementara.

    yield.tap do |rendered_partial|
      collection_cache.write(key, rendered_partial, cache_options)
    end
Pembuat Tongkat
sumber
9

Variasi dari jawaban @ sawa:

Seperti yang telah disebutkan, penggunaan tapmembantu mencari tahu maksud kode Anda (meski tidak selalu membuatnya lebih ringkas).

Dua fungsi berikut sama panjangnya, tetapi yang pertama Anda harus membaca sampai akhir untuk mencari tahu mengapa saya menginisialisasi Hash kosong di awal.

def tapping1
  # setting up a hash
  h = {}
  # working on it
  h[:one] = 1
  h[:two] = 2
  # returning the hash
  h
end

Di sini, di sisi lain, Anda tahu sejak awal bahwa hash yang diinisialisasi akan menjadi keluaran blok (dan, dalam hal ini, nilai kembalian fungsi).

def tapping2
  # a hash will be returned at the end of this block;
  # all work will occur inside
  Hash.new.tap do |h|
    h[:one] = 1
    h[:two] = 2
  end
end
Giuseppe
sumber
penerapan ini tapmenghasilkan argumen yang lebih menarik. Saya setuju dengan orang lain bahwa ketika Anda melihat user = User.new, maksudnya sudah jelas. Struktur data anonim, bagaimanapun, dapat digunakan untuk apa saja, dan tapmetode setidaknya menjelaskan bahwa struktur data adalah fokus dari metode tersebut.
volx757
Tidak yakin contoh ini lebih baik dan penggunaan pembandingan vs def tapping1; {one: 1, two: 2}; endacara .tapsekitar 50% lebih lambat dalam kasus ini
lacostenycoder
9

Ini adalah penolong untuk rantai panggilan. Ini melewati objeknya ke dalam blok yang diberikan dan, setelah blok selesai, mengembalikan objek:

an_object.tap do |o|
  # do stuff with an_object, which is in o #
end  ===> an_object

Manfaatnya adalah ketukan selalu mengembalikan objek yang dipanggil, bahkan jika blok mengembalikan beberapa hasil lain. Dengan demikian, Anda dapat memasukkan blok keran ke tengah jalur pipa metode yang ada tanpa merusak aliran.

Pushp Raj Saurabh
sumber
8

Saya akan mengatakan bahwa tidak ada keuntungan menggunakan tap. Satu-satunya manfaat potensial, seperti yang ditunjukkan @sawa adalah, dan saya mengutip: "Seorang pembaca tidak perlu membaca apa yang ada di dalam blok untuk mengetahui bahwa pengguna instance dibuat." Namun, pada saat itu argumen dapat dibuat bahwa jika Anda melakukan logika pembuatan rekaman non-simplistik, maksud Anda akan lebih baik dikomunikasikan dengan mengekstrak logika itu ke dalam metodenya sendiri.

Saya berpegang pada pendapat bahwa tapbeban yang tidak perlu pada pembacaan kode, dan dapat dilakukan tanpa, atau diganti dengan teknik yang lebih baik, seperti Metode Ekstrak .

Meskipun tapmerupakan metode kenyamanan, ini juga merupakan preferensi pribadi. Berikan tapmencoba. Kemudian tulis beberapa kode tanpa menggunakan tap, lihat apakah Anda menyukai satu cara di atas yang lain.

gylaz.dll
sumber
4

Mungkin ada sejumlah kegunaan dan tempat di mana kami dapat menggunakannya tap. Sejauh ini saya hanya menemukan 2 penggunaan berikut tap.

1) Tujuan utama dari metode ini adalah untuk memanfaatkan rantai metode, untuk melakukan operasi pada hasil antara dalam rantai tersebut. yaitu

(1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
    tap    { |x| puts "array: #{x.inspect}" }.
    select { |x| x%2 == 0 }.
    tap    { |x| puts "evens: #{x.inspect}" }.
    map    { |x| x*x }.
    tap    { |x| puts "squares: #{x.inspect}" }

2) Apakah Anda pernah menemukan diri Anda memanggil metode pada beberapa objek, dan nilai kembaliannya tidak seperti yang Anda inginkan? Mungkin Anda ingin menambahkan nilai arbitrer ke sekumpulan parameter yang disimpan dalam hash. Anda memperbaruinya dengan Hash. [] , Tetapi Anda mendapatkan bilah belakang alih-alih hash params, jadi Anda harus mengembalikannya secara eksplisit. yaitu

def update_params(params)
  params[:foo] = 'bar'
  params
end

Untuk mengatasi situasi ini di sini, tapmetode ikut bermain. Sebut saja pada objek, lalu lewati tap blok dengan kode yang ingin Anda jalankan. Objek tersebut akan diserahkan ke blok tersebut, kemudian dikembalikan. yaitu

def update_params(params)
  params.tap {|p| p[:foo] = 'bar' }
end

Ada lusinan kasus penggunaan lainnya, coba temukan sendiri :)

Sumber:
1) API Dock Object tap
2) lima-ruby-metode-Anda-harus-menggunakan

Aamir
sumber
3

Anda benar: penggunaan tapdalam contoh Anda tidak ada gunanya dan mungkin kurang bersih daripada alternatif Anda.

Sebagai catatan Rebitzele, taphanyalah metode kenyamanan, sering digunakan untuk membuat referensi yang lebih pendek ke objek saat ini.

Salah satu kasus penggunaan yang baik tapadalah untuk debugging: Anda dapat memodifikasi objek, mencetak status saat ini, lalu melanjutkan memodifikasi objek di blok yang sama. Lihat di sini misalnya: http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions .

Saya kadang-kadang suka menggunakan tapmetode di dalam untuk kembali bersyarat lebih awal sambil mengembalikan objek saat ini sebaliknya.

Jacob Brown
sumber
Ini juga merupakan aplikasi yang disebutkan dalam dokumen: ruby-doc.org/core-2.1.3/Object.html#method-i-tap
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
3

Ada alat yang disebut flog yang mengukur seberapa sulit membaca suatu metode. "Semakin tinggi skornya, semakin sulit kode tersebut."

def with_tap
  user = User.new.tap do |u|
    u.username = "foobar"
    u.save!
  end
end

def without_tap
  user = User.new
  user.username = "foobar"
  user.save!
end

def using_create
  user = User.create! username: "foobar"
end

dan menurut hasil cambuk, metode dengan tapyang paling sulit dibaca (dan saya setuju dengan itu)

 4.5: main#with_tap                    temp.rb:1-4
 2.4:   assignment
 1.3:   save!
 1.3:   new
 1.1:   branch
 1.1:   tap

 3.1: main#without_tap                 temp.rb:8-11
 2.2:   assignment
 1.1:   new
 1.1:   save!

 1.6: main#using_create                temp.rb:14-16
 1.1:   assignment
 1.1:   create!
Evmorov
sumber
1

Anda dapat membuat kode Anda lebih modular menggunakan tap, dan dapat mencapai pengelolaan variabel lokal yang lebih baik. Misalnya, dalam kode berikut, Anda tidak perlu menetapkan variabel lokal ke objek yang baru dibuat, dalam lingkup metode. Perhatikan bahwa variabel blok, u , dibatasi di dalam blok. Ini sebenarnya adalah salah satu keindahan kode ruby.

def a_method
  ...
  name = "foobar"
  ...
  return User.new.tap do |u|
    u.username = name
    u.save!
  end
end
pengguna3936126
sumber
1

Di rails kita dapat menggunakan tapparameter whitelist secara eksplisit:

def client_params
    params.require(:client).permit(:name).tap do |whitelist|
        whitelist[:name] = params[:client][:name]
    end
end
Ashan Priyadarshana
sumber
1

Saya akan memberikan contoh lain yang telah saya gunakan. Saya memiliki metode user_params yang mengembalikan params yang diperlukan untuk disimpan untuk pengguna (ini adalah proyek Rails)

def user_params
  params.require(:user).permit(
    :first_name,
    :last_name,
    :email,
    :address_attributes
  )
end

Anda dapat melihat saya tidak mengembalikan apa pun kecuali ruby ​​mengembalikan output dari baris terakhir.

Kemudian, setelah beberapa saat, saya perlu menambahkan atribut baru secara kondisional. Jadi, saya mengubahnya menjadi seperti ini:

def user_params 
  u_params = params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  )
  u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  u_params
end

Di sini kita dapat menggunakan tap untuk menghapus variabel lokal dan menghapus return:

def user_params 
  params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  ).tap do |u_params|
    u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  end
end
rubyprince
sumber
1

Di dunia di mana pola pemrograman fungsional menjadi praktik terbaik ( https://maryrosecook.com/blog/post/a-praktis-introduction-to-functional-programming ), Anda dapat melihat tap, sebagai mapsatu nilai, memang , untuk mengubah data Anda pada rantai transformasi.

transformed_array = array.map(&:first_transformation).map(&:second_transformation)

transformed_value = item.tap(&:first_transformation).tap(&:second_transformation)

Tidak perlu mendeklarasikan itemberkali-kali di sini.

Augustin Riedinger
sumber
0

Apa bedanya?

Perbedaan dalam hal keterbacaan kode adalah gaya belaka.

Kode Berjalan melalui:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

Poin utama:

  • Perhatikan bagaimana uvariabel sekarang digunakan sebagai parameter blok?
  • Setelah pemblokiran selesai, uservariabel sekarang harus mengarah ke Pengguna (dengan nama pengguna: 'foobar', dan siapa yang juga disimpan).
  • Hanya menyenangkan dan lebih mudah dibaca.

Dokumentasi API

Berikut adalah versi kode sumber yang mudah dibaca:

class Object
  def tap
    yield self
    self
  end
end

Untuk info lebih lanjut, lihat tautan berikut:

https://apidock.com/ruby/Object/tap

http://ruby-doc.org/core-2.2.3/Object.html#method-i-tap

BKSpurgeon
sumber