Sepertinya kode F # sering pola cocok dengan tipe. Pasti
match opt with
| Some val -> Something(val)
| None -> Different()
sepertinya biasa.
Tapi dari perspektif OOP, yang terlihat sangat mengerikan seperti aliran kontrol berdasarkan pemeriksaan tipe runtime, yang biasanya akan disukai. Untuk mengejanya, dalam OOP Anda mungkin lebih suka menggunakan overloading:
type T =
abstract member Route : unit -> unit
type Foo() =
interface T with
member this.Route() = printfn "Go left"
type Bar() =
interface T with
member this.Route() = printfn "Go right"
Ini tentunya lebih banyak kode. OTOH, menurut pikiran saya OOP-y memiliki kelebihan struktural:
- ekstensi ke bentuk baru
T
mudah; - Saya tidak perlu khawatir menemukan duplikasi aliran kontrol pemilihan rute; dan
- Pilihan rute tidak dapat diubah dalam arti bahwa begitu saya ada
Foo
di tangan, saya tidak perlu khawatir tentangBar.Route()
implementasi rute
Apakah ada keuntungan dari pencocokan pola dengan jenis yang tidak saya lihat? Apakah itu dianggap idiomatis atau apakah itu kemampuan yang tidak umum digunakan?
object-oriented
functional-programming
f#
pattern-matching
Larry OBrien
sumber
sumber
But from an OOP perspective, that looks an awful lot like control-flow based on a runtime type check, which would typically be frowned on.
- terdengar terlalu dogmatis. Kadang-kadang, Anda ingin memisahkan ops Anda dari hierarki Anda: mungkin 1) Anda tidak dapat menambahkan op ke hierarki b / c Anda tidak memiliki hierarki; 2) kelas-kelas yang Anda inginkan untuk op tidak cocok dengan hierarki Anda; 3) Anda bisa menambahkan op ke hierarki Anda, tetapi tidak ingin b / c Anda tidak ingin mengacaukan API hierarki Anda dengan banyak omong kosong yang kebanyakan klien tidak gunakan.Some
danNone
bukan tipe. Keduanya adalah konstruktor yang tipenyaforall a. a -> option a
danforall a. option a
(maaf, tidak yakin apa sintaks untuk tipe anotasi dalam F #).Jawaban:
Anda benar bahwa hierarki kelas OOP sangat terkait erat dengan serikat yang didiskriminasi dalam F # dan bahwa pencocokan pola sangat erat terkait dengan tes tipe dinamis. Sebenarnya, ini sebenarnya bagaimana F # mengkompilasi serikat yang didiskriminasi ke .NET!
Mengenai ekstensibilitas, ada dua sisi masalah:
Yang mengatakan, F # akan memberi Anda peringatan ketika Anda melewatkan kasus dalam pencocokan pola, jadi menambahkan kasus serikat baru sebenarnya tidak terlalu buruk.
Mengenai menemukan duplikasi dalam pemilihan root - F # akan memberi Anda peringatan ketika Anda memiliki kecocokan yang duplikat, misalnya:
Fakta bahwa "pilihan rute tidak berubah" mungkin juga bermasalah. Misalnya, jika Anda ingin berbagi implementasi fungsi antara
Foo
danBar
kasus, tetapi melakukan sesuatu yang lain untukZoo
kasus ini, Anda bisa menyandikan itu dengan mudah menggunakan pencocokan pola:Secara umum, FP lebih fokus pada pertama merancang jenis dan kemudian menambahkan fungsi. Jadi itu benar-benar mendapat manfaat dari fakta bahwa Anda dapat menyesuaikan jenis Anda (model domain) dalam beberapa baris dalam satu file dan kemudian dengan mudah menambahkan fungsi yang beroperasi pada model domain.
Kedua pendekatan - OO dan FP cukup saling melengkapi dan keduanya memiliki kelebihan dan kekurangan. Yang sulit (berasal dari perspektif OO) adalah F # biasanya menggunakan gaya FP sebagai default. Tetapi jika benar-benar ada lebih banyak kebutuhan untuk menambahkan sub-kelas baru, Anda selalu dapat menggunakan antarmuka. Tetapi di sebagian besar sistem, Anda sama-sama perlu menambahkan jenis dan fungsi, sehingga pilihannya benar-benar tidak terlalu penting - dan menggunakan serikat yang didiskriminasi dalam F # lebih baik.
Saya akan merekomendasikan seri blog yang hebat ini untuk informasi lebih lanjut.
sumber
Anda telah mengamati dengan benar bahwa pencocokan pola (pada dasarnya pernyataan sakelar supercharged) dan pengiriman dinamis memiliki kesamaan. Mereka juga hidup berdampingan dalam beberapa bahasa, dengan hasil yang sangat menyenangkan. Namun, ada sedikit perbedaan.
Saya mungkin menggunakan sistem tipe untuk mendefinisikan tipe yang hanya dapat memiliki jumlah subtipe tetap:
Tidak akan pernah ada subtipe lain dari
Bool
atauOption
, jadi subclass tampaknya tidak berguna (beberapa bahasa seperti Scala memiliki gagasan tentang subclass yang dapat menangani hal ini - kelas dapat ditandai sebagai "final" di luar unit kompilasi saat ini, tetapi subtipe dapat didefinisikan di dalam unit kompilasi ini).Karena subtipe jenis seperti
Option
sekarang dikenal secara statis , kompiler dapat memperingatkan jika kita lupa untuk menangani kasus dalam kecocokan pola kita. Ini berarti bahwa kecocokan pola lebih seperti downcast khusus yang memaksa kita untuk menangani semua opsi.Selain itu, pengiriman metode dinamis (yang diperlukan untuk OOP) juga menyiratkan pemeriksaan tipe runtime, tetapi dari jenis yang berbeda. Oleh karena itu cukup tidak relevan jika kita melakukan pemeriksaan jenis ini secara eksplisit melalui kecocokan pola atau secara implisit melalui pemanggilan metode.
sumber
Pencocokan pola F # biasanya dilakukan dengan serikat terdiskriminasi daripada dengan kelas (dan dengan demikian tidak secara teknis jenis-cek sama sekali). Ini memungkinkan kompiler untuk memberi Anda peringatan ketika Anda tidak memperhitungkan kasus dalam pencocokan pola.
Hal lain yang perlu diperhatikan adalah bahwa dalam gaya fungsional, Anda mengatur berbagai hal berdasarkan fungsionalitas bukan oleh data, sehingga pencocokan pola memungkinkan Anda untuk mengumpulkan fungsionalitas yang berbeda di satu tempat daripada tersebar di seluruh kelas. Ini juga memiliki keuntungan bahwa Anda dapat melihat bagaimana kasus-kasus lain ditangani tepat di sebelah tempat Anda perlu melakukan perubahan.
Menambahkan opsi baru akan terlihat seperti:
sumber
Sebagian, Anda melihatnya lebih sering dalam pemrograman fungsional karena Anda menggunakan tipe untuk membuat keputusan lebih sering. Saya menyadari bahwa Anda mungkin hanya memilih contoh kurang lebih secara acak, tetapi OOP yang setara dengan contoh pencocokan pola Anda akan lebih sering terlihat seperti:
Dengan kata lain, relatif jarang menggunakan polimorfisme untuk menghindari hal-hal rutin seperti cek kosong di OOP. Sama seperti seorang programmer OO tidak membuat objek nol dalam setiap situasi kecil, seorang programmer fungsional tidak selalu membebani suatu fungsi, terutama ketika Anda tahu daftar pola Anda dijamin lengkap. Jika Anda menggunakan sistem tipe dalam lebih banyak situasi, Anda akan melihatnya menggunakannya dengan cara yang tidak biasa Anda lakukan.
Sebaliknya, pemrograman fungsional idiomatis yang setara dengan contoh OOP Anda kemungkinan besar tidak akan menggunakan pencocokan pola, tetapi akan memiliki
fooRoute
danbarRoute
fungsi yang akan diteruskan sebagai argumen ke kode panggilan. Jika seseorang menggunakan pencocokan pola dalam situasi itu, biasanya dianggap salah, seperti halnya seseorang yang mengaktifkan tipe akan dianggap salah dalam OOP.Jadi kapan pencocokan pola dianggap sebagai kode pemrograman fungsional yang baik? Ketika Anda melakukan lebih dari sekedar melihat jenisnya, dan ketika memperpanjang persyaratan tidak akan membutuhkan penambahan lebih banyak kasus. Sebagai contoh,
Some val
tidak hanya memeriksa yangopt
memiliki tipeSome
, itu juga mengikatval
ke tipe yang mendasari untuk penggunaan tipe-aman di sisi lain->
. Anda tahu bahwa Anda kemungkinan besar tidak akan pernah membutuhkan kasus ketiga, jadi ini adalah penggunaan yang baik.Pencocokan pola mungkin secara dangkal menyerupai pernyataan beralih berorientasi objek, tetapi ada banyak lagi yang terjadi, terutama dengan pola yang lebih panjang atau bersarang. Pastikan Anda memperhitungkan semua yang dilakukannya sebelum menyatakannya setara dengan beberapa kode OOP yang dirancang dengan buruk. Seringkali, ini secara ringkas menangani situasi yang tidak dapat diwakili secara bersih dalam hierarki warisan.
sumber
Some
danNone
bukan tipe, jadi Anda tidak mencocokkan pola pada tipe. Anda pola-pertandingan pada konstruktor dari jenis yang sama . Ini tidak seperti menanyakan "instanceof".