Saya sadar saya datang terlambat ke pesta, tetapi Anda punya dua jawaban teoretis di sini, dan saya ingin memberikan alternatif praktis untuk dikunyah. Saya datang pada saat ini sebagai noob Haskell kerabat yang masih baru-baru ini dipaksa melalui subjek Arrows untuk proyek yang sedang saya kerjakan.
Pertama, Anda dapat secara produktif menyelesaikan sebagian besar masalah di Haskell tanpa meraih Arrows. Beberapa Haskellers terkenal benar-benar tidak suka dan tidak menggunakannya (lihat di sini , di sini , dan di sini untuk lebih lanjut tentang ini). Jadi, jika Anda berkata pada diri sendiri, "Hei, aku tidak butuh ini," pahamilah bahwa Anda mungkin benar.
Apa yang saya temukan paling frustasi tentang Arrows ketika saya pertama kali mempelajarinya adalah bagaimana tutorial tentang masalah tersebut tidak dapat dihindarkan untuk analogi sirkuit. Jika Anda melihat kode Arrow - variasi bergula, setidaknya - itu tidak mirip dengan Hardware Defnition Language. Input Anda berbaris di sebelah kanan, output Anda di sebelah kiri, dan jika Anda gagal memasang semuanya dengan benar, mereka hanya gagal untuk menyala. Saya berpikir dalam hati: Benarkah? Di sinilah kita berakhir? Sudahkah kita menciptakan bahasa dengan tingkat yang sangat tinggi sehingga sekali lagi terdiri dari kabel tembaga dan solder?
Jawaban yang benar untuk ini, sejauh yang saya dapat tentukan, adalah: Sebenarnya, ya. Kasing pembunuh sekarang untuk Arrows adalah FRP (pikirkan Yampa, game, musik, dan sistem reaktif secara umum). Masalah yang dihadapi FRP sebagian besar adalah masalah yang sama yang dihadapi semua sistem pesan sinkron lainnya: bagaimana cara mengirim aliran input yang kontinu ke dalam aliran output yang kontinu tanpa menjatuhkan informasi yang relevan atau memunculkan kebocoran. Anda dapat memodelkan stream sebagai daftar - beberapa sistem FRP terbaru menggunakan pendekatan ini - tetapi ketika Anda memiliki banyak input, daftar menjadi hampir mustahil untuk dikelola. Anda perlu melindungi diri dari arus.
Apa yang diperbolehkan Arrows dalam sistem FRP adalah komposisi fungsi ke dalam jaringan sementara pada saat yang sama sepenuhnya mengabstraksi semua referensi sama sekali dengan nilai-nilai dasar yang dilewati oleh fungsi-fungsi tersebut. Jika Anda baru mengenal FP, ini bisa membingungkan pada awalnya, dan kemudian mengejutkan ketika Anda menyerap implikasinya. Anda baru saja menyerap gagasan bahwa fungsi dapat diabstraksikan, dan bagaimana memahami daftar seperti [(*), (+), (-)]
sedang mengetik [(a -> a -> a)]
. Dengan Panah, Anda dapat mendorong abstraksi satu lapis lebih jauh.
Kemampuan tambahan ini untuk abstrak membawa bahaya sendiri. Untuk satu hal, itu dapat mendorong GHC ke sudut kasus di mana ia tidak tahu apa yang harus dilakukan dari asumsi tipe Anda. Anda harus siap untuk berpikir pada level tipe - ini adalah kesempatan yang sangat baik untuk belajar tentang jenis dan PeringkatN dan jenis topik lainnya.
Ada juga sejumlah contoh dari apa yang saya sebut "Stupid Arrow Stunts" di mana pembuat kode meraih beberapa combinator Arrow hanya karena dia ingin memamerkan trik rapi dengan tupel. (Ini kontribusi saya sendiri yang sepele untuk kegilaan .) Jangan ragu untuk mengabaikan hot dogging ketika Anda menemukan itu di alam liar.
CATATAN: Seperti yang saya sebutkan di atas, saya adalah noob relatif. Jika saya telah mengumumkan kesalahpahaman di atas, jangan ragu untuk memperbaikinya.
removeAt' n = arr(\ xs -> (xs,xs)) >>> arr (take (n-1)) *** arr (drop n) >>> arr (uncurry (++)) >>> returnA
dapat ditulis dengan lebih ringkas dan jelasremoveAt' n = (arr (take $ n-1) &&& arr (drop n)) >>> (arr $ uncurry (++))
.Ini semacam jawaban "lunak", dan saya tidak yakin apakah ada referensi yang menyatakannya dengan cara ini, tapi begitulah cara saya memikirkan panah:
Jenis panah
A b c
pada dasarnya adalah fungsib -> c
tetapi dengan struktur lebih banyak dengan cara yang sama bahwa nilai monadikM a
memiliki lebih banyak struktur daripada yang biasaa
.Sekarang struktur tambahan itu tergantung pada contoh panah tertentu yang sedang Anda bicarakan. Sama seperti dengan monad
IO a
danMaybe a
masing-masing memiliki struktur tambahan yang berbeda.Hal yang Anda dapatkan dengan monad adalah ketidakmampuan untuk berpindah dari satu
M a
ke yang laina
. Sekarang ini mungkin tampak seperti batasan, tetapi sebenarnya fitur: sistem tipe melindungi Anda dari mengubah nilai monadik menjadi nilai lama yang sederhana. Anda hanya dapat menggunakan nilai dengan berpartisipasi dalam monad melalui>>=
atau operasi primitif dari instance monad tertentu.Demikian juga hal yang Anda dapatkan
A b c
adalah ketidakmampuan untuk membangun "fungsi" penghasil-c yang memproduksi-b baru. Panah melindungi Anda dari mengkonsumsib
dan membuatc
kecuali dengan berpartisipasi dalam berbagai combinator panah atau dengan menggunakan operasi primitif dari instance panah tertentu.Misalnya fungsi sinyal di Yampa kira-kira
(Time -> a) -> (Time -> b)
, tetapi selain itu mereka harus mematuhi batasan kausalitas tertentu : output pada waktut
ditentukan oleh nilai-nilai masa lalu dari sinyal input: Anda tidak dapat melihat ke masa depan. Jadi yang mereka lakukan adalah alih-alih pemrograman dengan(Time -> a) -> (Time -> b)
, Anda memprogram denganSF a b
dan Anda membangun fungsi sinyal Anda dari primitif. Kebetulan karenaSF a b
berperilaku sangat mirip fungsi, sehingga struktur umum adalah apa yang disebut "panah".sumber
b
dan membuatc
kecuali dengan berpartisipasi dalam berbagai combinator panah atau dengan menggunakan operasi primitif dari instance panah tertentu." Dengan permintaan maaf karena telah menjawab jawaban kuno ini: kalimat ini membuat saya berpikir tentang tipe linier, yaitu bahwa sumber daya tidak dapat dikloning atau dihilangkan. Apakah Anda pikir ada koneksi?Saya suka berpikir tentang Arrows, seperti Monads dan Functors, sebagai memungkinkan programmer untuk melakukan komposisi fungsi yang eksotis.
Tanpa Monads atau Arrows (dan Functors), komposisi fungsi dalam bahasa fungsional terbatas untuk menerapkan satu fungsi ke hasil fungsi lainnya. Dengan monad dan functors, Anda dapat menetapkan dua fungsi, dan kemudian menulis kode reusable terpisah yang menentukan bagaimana fungsi-fungsi tersebut, dalam konteks monad tertentu, berinteraksi satu sama lain dan dengan data yang diteruskan ke dalamnya. Kode ini ditempatkan di dalam kode ikat Monad. Jadi monad adalah satu tampilan, hanya wadah untuk kode ikat yang dapat digunakan kembali. Fungsi menyusun secara berbeda dalam konteks satu monad dari monad lain.
Contoh sederhana adalah Mungkin monad, di mana ada kode dalam fungsi bind sedemikian rupa sehingga jika fungsi A disusun dengan fungsi B dalam monad Mungkin, dan B menghasilkan Nothing, maka kode bind akan memastikan bahwa komposisi dua fungsi menghasilkan A Nothing, tanpa repot-repot menerapkan A ke nilai Nothing yang keluar dari B. Jika tidak ada monad, programmer harus menulis kode ke A untuk menguji input Nothing.
Monads juga berarti bahwa pemrogram tidak perlu mengetik secara eksplisit parameter yang diperlukan setiap fungsi ke dalam kode sumber - fungsi bind menangani parameter yang lewat. Jadi menggunakan monad, kode sumber dapat mulai terlihat lebih seperti rantai statis nama fungsi, daripada tampak seolah-olah fungsi A "memanggil" fungsi B dengan parameter C dan D - kode mulai terasa lebih seperti rangkaian elektronik daripada memindahkan mesin - lebih fungsional daripada keharusan.
Panah juga menghubungkan fungsi bersama dengan fungsi bind, menyediakan fungsionalitas yang dapat digunakan kembali dan menyembunyikan parameter. Tetapi Arrows sendiri dapat dihubungkan bersama-sama dan disusun, dan secara opsional dapat merutekan data ke Arrows lain pada saat runtime. Sekarang Anda dapat menerapkan data ke dua jalur Arrows, yang "melakukan hal yang berbeda" ke data, dan menyusun kembali hasilnya. Atau Anda dapat memilih cabang Arrows mana untuk meneruskan data, tergantung pada beberapa nilai dalam data. Kode yang dihasilkan bahkan lebih mirip sirkuit elektronik, dengan sakelar, penundaan, integrasi dll. Program ini terlihat sangat statis, dan Anda seharusnya tidak dapat melihat banyak manipulasi data yang terjadi. Ada lebih sedikit dan lebih sedikit parameter untuk dipikirkan, dan lebih sedikit yang perlu dipikirkan tentang nilai yang mungkin atau tidak mungkin diambil parameter.
Menulis program Arrowized sebagian besar melibatkan memilih dari rak Panah seperti splitter, switch, delay dan integrator, mengangkat fungsi ke dalam Arrows, dan menghubungkan Arrows bersama-sama untuk membentuk Arrows yang lebih besar. Dalam Pemrograman Fungsional Reaktif Arrowized, Arrows membentuk loop, dengan input dari dunia yang dikombinasikan dengan output dari iterasi terakhir dari program, sehingga output bereaksi terhadap input dunia nyata.
Salah satu nilai dunia nyata adalah waktu. Di Yampa, Signal Function Arrow secara tak terlihat memasukkan parameter waktu melalui program komputer - Anda tidak pernah mengakses nilai waktu, tetapi jika Anda menghubungkan panah integrator ke dalam program, ia akan menampilkan nilai yang terintegrasi dari waktu ke waktu yang kemudian dapat Anda gunakan untuk meneruskan ke panah lainnya.
sumber
Hanya tambahan untuk jawaban lain: Secara pribadi itu banyak membantu saya untuk memahami apa konsep seperti itu (secara matematis) dan bagaimana hubungannya dengan konsep lain yang saya tahu.
Dalam kasus panah, saya menemukan makalah berikut ini membantu - membandingkan monad, fungsi aplikator (idiom) dan panah: Idiom tidak menyadari, panah sangat teliti, monad adalah pilihan bebas oleh Sam Lindley, Philip Wadler dan Jeremy Yallop.
Saya juga percaya tidak ada yang menyebutkan tautan ini yang dapat memberi Anda beberapa ide dan literatur tentang masalah ini.
sumber