Menggunakan antrian untuk memisahkan fungsi / menghindari panggilan langsung?

8

Jenis pertanyaan pemula pemrograman fungsional di sini:

Saya telah membaca transkrip beberapa pembicaraan Rich Hickey, dan dalam beberapa ceramahnya yang lebih terkenal, dia merekomendasikan penggunaan antrian sebagai alternatif agar fungsi-fungsi saling memanggil satu sama lain. (Misalnya dalam Desain, Komposisi dan Kinerja dan dalam Simple Made Easy .)

Saya tidak begitu mengerti hal ini, dalam beberapa hal:

  1. Apakah dia berbicara tentang memasukkan data dalam antrian dan kemudian meminta setiap fungsi menggunakannya? Jadi alih-alih fungsi A memanggil fungsi B untuk melakukan komputasi sendiri, kita hanya memiliki fungsi B menampar outputnya pada antrian dan kemudian memiliki fungsi A ambil itu? Atau, sebagai alternatif, apakah kita berbicara tentang menempatkan fungsi pada antrian dan kemudian secara berturut-turut menerapkannya pada data (tentu saja tidak, karena itu akan melibatkan mutasi masif, bukan? Dan juga multiplikasi antrian untuk fungsi multi-arity, atau seperti pohon atau sesuatu? )

  2. Bagaimana hal itu membuat segalanya lebih sederhana? Intuisi saya adalah bahwa strategi ini akan menciptakan lebih banyak kompleksitas, karena antrian akan menjadi semacam keadaan, dan kemudian Anda harus khawatir "bagaimana jika beberapa fungsi lain menyelinap masuk dan memasukkan beberapa data di atas antrian?"

Satu jawaban untuk pertanyaan implementasi pada SO menunjukkan bahwa idenya adalah menciptakan sekelompok antrian yang berbeda. Jadi setiap fungsi menempatkan output dalam antrian sendiri (??). Tapi itu juga membingungkan saya, karena jika Anda menjalankan fungsi sekali, maka mengapa perlu antrian untuk outputnya ketika Anda bisa mengambil output itu dan menampar nama itu sebagai (var, atom, entri dalam besar tabel hash, apa pun). Sebaliknya, jika suatu fungsi berjalan beberapa kali, dan Anda menempelkan outputnya ke antrian, maka Anda telah membuat status pada diri Anda lagi, dan Anda harus khawatir tentang urutan semua hal yang dipanggil, fungsi hilir menjadi kurang murni, dll.

Jelas saya tidak mengerti maksudnya di sini. Bisakah seseorang menjelaskan sedikit?

Paul Gowder
sumber
Saya tidak melihat satu pun referensi ke antrian di tautan pertama Anda, meskipun saya akan memberi Anda bahwa posnya sangat panjang dan saya mungkin melewatkannya. Sepertinya itu lebih merupakan pembicaraan tentang seni daripada tentang pemrograman.
Robert Harvey
Antrian disebutkan secara singkat dua kali pada artikel kedua, tetapi tidak diuraikan secara terperinci. Bagaimanapun, ide untuk menggunakan antrian pesan untuk berkomunikasi antara aplikasi atau modul telah ada untuk sementara waktu. Tampaknya tidak mungkin bahwa Anda akan melakukan ini dalam satu aplikasi kecuali Anda membuat pipa pemrosesan atau mesin keadaan.
Robert Harvey
Ada dalam paragraf di bawah slide yang berjudul "Bongkar waktu / urutan / aliran" ("Anda dapat memecah sistem, jadi ada lebih sedikit panggilan langsung. Anda dapat menggunakan antrian untuk melakukan itu.")
Paul Gowder
3
Tidak layak untuk membuat jawaban yang lengkap, tetapi dia benar-benar hanya memberikan deskripsi kabur untuk konsep kumpulan pekerjaan dan pemrograman berdasarkan acara. Jadi, Anda mengemas panggilan fungsi ke Jobobjek umum , mendorongnya ke dalam antrian, dan membuat satu atau lebih utas pekerja bekerja pada antrian itu. The Jobkemudian mengirimkan lebih Jobs ke dalam antrian setelah selesai. Nilai pengembalian digantikan oleh panggilan balik dalam konsep itu. Ini mimpi buruk untuk debug dan verifikasi karena Anda tidak memiliki tumpukan panggilan, dan efisien dan fleksibel untuk alasan yang sama.
Ext3h
Terima kasih. Mungkin masalah saya yang sebenarnya adalah saya tidak mengerti pesan! (Heh, waktunya pergi belajar smalltalk? :-))
Paul Gowder

Jawaban:

6

Ini lebih merupakan latihan desain daripada rekomendasi umum. Anda biasanya tidak akan membuat antrian di antara semua panggilan fungsi langsung Anda. Itu akan konyol. Namun, jika Anda tidak mendesain fungsi Anda seolah-olah antrian mungkin dimasukkan di antara salah satu panggilan fungsi langsung, Anda tidak dapat secara adil mengklaim bahwa Anda telah menulis kode yang dapat digunakan kembali dan dapat dibuat. Itulah intinya yang dibuat oleh Rich Hickey.

Ini adalah alasan utama di balik kesuksesan Apache Spark , misalnya. Anda menulis kode yang sepertinya membuat panggilan fungsi langsung pada koleksi lokal, dan kerangka kerja menerjemahkan kode itu ke meneruskan pesan pada antrian antara node cluster. Jenis gaya pengkodean yang sederhana, dapat disusun, dapat digunakan kembali, dan pendukung Rich Hickey memungkinkan hal itu.

Karl Bielefeldt
sumber
Tapi bukankah itu hanya perubahan dalam proses pengikatan metode? Pada akhirnya, panggilan fungsi hanyalah panggilan fungsi, bukan? Apa yang terjadi setelah itu tergantung pada apa fungsinya. Jadi sepertinya kurang tentang membuat panggilan fungsi daripada tentang bagaimana infrastruktur yang mendasarinya dirancang.
Robert Harvey
1
Dengan kata lain, perubahan apa yang akan Anda lakukan pada panggilan fungsi Anda untuk membuatnya "ramah-antrian?"
Robert Harvey
5
Ingat, kode di ujung antrian tidak harus memiliki akses ke memori dan IO yang sama. Fungsi ramah antrian bebas dari efek samping dan mengharapkan input dan menghasilkan output yang tidak berubah dan mudah serial. Itu tidak mudah untuk ditemui di banyak basis kode.
Karl Bielefeldt
3
Ah, jadi "pemrograman fungsional ramah" kalau begitu. Agak masuk akal, karena Rich Hickey mendiskusikannya.
Robert Harvey
0

Satu hal yang perlu diperhatikan adalah pemrograman fungsional memungkinkan Anda untuk menghubungkan fungsi satu sama lain secara tidak langsung melalui objek mediator yang menangani pengadaan argumen untuk dimasukkan ke dalam fungsi dan secara fleksibel mengarahkan hasilnya ke penerima yang menginginkan hasil mereka. Jadi misalkan Anda memiliki beberapa kode panggilan langsung langsung yang terlihat seperti contoh ini, di Haskell:

myThing :: A -> B -> C
myThing a b = f a (g a b)

Nah, menggunakan Applicativekelas Haskell <$>dan <*>operatornya dan kita dapat secara otomatis menulis ulang kode itu menjadi ini:

myThing :: Applicative f => f A -> f B -> f C
myThing a b = f <$> a <*> (g <$> a <*> b)

... di mana sekarang myThingtidak lagi secara langsung menelepon fdan g, melainkan menghubungkan mereka melalui beberapa mediator tipe f. Jadi misalnya, fbisa berupa beberapa Streamjenis yang disediakan oleh perpustakaan yang menyediakan antarmuka ke sistem antrian, dalam hal ini kita akan memiliki jenis ini:

myThing :: Stream A -> Stream B -> Stream C
myThing a b = f <$> a <*> (g <$> a <*> b)

Sistem seperti ini memang ada. Bahkan, Anda dapat melihat aliran Java 8 sebagai versi dari paradigma ini. Anda mendapatkan kode seperti ini:

List<Integer> transactionsIds = 
    transactions.parallelStream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Di sini Anda menggunakan fungsi-fungsi berikut:

  • t -> t.getType() == Transaction.GROCERY
  • comparing(Transaction::getValue).reversed()
  • Transaction::getId
  • toList()

... dan bukannya meminta mereka saling menelepon secara langsung, Anda menggunakan Streamsistem untuk menengahi di antara mereka. Contoh kode ini tidak memanggil Transaction::getIdfungsi secara langsung — Streammemanggilnya dengan transaksi yang selamat dari yang sebelumnya filter. Anda dapat menganggap Streamantrian sebagai jenis minimal yang berfungsi pasangan secara tidak langsung dan merutekan nilai di antara mereka.

sakundim
sumber