Setidaknya ada 4 perpustakaan yang saya sadari menyediakan lensa.
Gagasan lensa adalah bahwa lensa menyediakan sesuatu yang isomorfik
data Lens a b = Lens (a -> b) (b -> a -> a)
menyediakan dua fungsi: pengambil dan penyetel
get (Lens g _) = g
put (Lens _ s) = s
tunduk pada tiga undang-undang:
Pertama, bahwa jika Anda meletakkan sesuatu, Anda bisa mendapatkannya kembali
get l (put l b a) = b
Kedua, mendapatkan dan mengatur tidak mengubah jawaban
put l (get l a) a = a
Dan ketiga, menempatkan dua kali sama dengan menempatkan satu kali, atau lebih tepatnya, yang kedua menang.
put l b1 (put l b2 a) = put l b1 a
Perhatikan, bahwa sistem tipe tidak cukup untuk memeriksa undang-undang ini untuk Anda, jadi Anda harus memastikannya sendiri tidak peduli apa pun penerapan lensa yang Anda gunakan.
Banyak perpustakaan ini juga menyediakan banyak combinator tambahan di atas, dan biasanya beberapa bentuk mesin haskell template untuk secara otomatis menghasilkan lensa untuk bidang tipe rekaman sederhana.
Dengan mengingat hal itu, kita dapat beralih ke implementasi yang berbeda:
Implementasi
fclabels
fclabels mungkin merupakan alasan paling mudah tentang perpustakaan lensa, karena itu a :-> b
dapat langsung diterjemahkan ke jenis di atas. Ini memberikan contoh Kategori(:->)
yang berguna karena memungkinkan Anda untuk membuat lensa. Ini juga menyediakan Point
jenis tanpa hukum yang menggeneralisasikan gagasan tentang lensa yang digunakan di sini, dan beberapa pipa ledeng untuk menangani isomorfisma.
Salah satu kendala untuk adopsi fclabels
adalah bahwa paket utama termasuk pipa templat-haskell, sehingga paket tersebut tidak Haskell 98, dan itu juga memerlukan TypeOperators
ekstensi (cukup non-kontroversial) .
pengakses data
[Sunting: data-accessor
tidak lagi menggunakan representasi ini, tetapi telah pindah ke bentuk yang mirip dengan data-lens
. Aku menyimpan komentar ini.]
Data-accessor agak lebih populer daripada fclabels
, sebagian karena merupakan Haskell 98. Namun, pilihannya dari representasi internal membuat saya muntah di mulut saya sedikit.
Jenis T
yang digunakan untuk mewakili lensa didefinisikan secara internal
newtype T r a = Cons { decons :: a -> r -> (a, r) }
Akibatnya, untuk get
nilai lensa, Anda harus mengirimkan nilai yang tidak ditentukan untuk argumen 'a'! Ini mengejutkan saya sebagai implementasi yang sangat jelek dan ad hoc.
Yang mengatakan, Henning telah memasukkan pipa templat-haskell untuk secara otomatis menghasilkan pengakses untuk Anda dalam paket ' data-accessor-templat ' terpisah.
Ini memiliki manfaat dari paket besar yang sudah digunakan, yaitu Haskell 98, dan memberikan Category
contoh yang sangat penting , jadi jika Anda tidak memperhatikan bagaimana sosis dibuat, paket ini sebenarnya pilihan yang cukup masuk akal .
lensa
Selanjutnya, ada paket lensa , yang mengamati bahwa lensa dapat memberikan homomorfisma monad negara antara dua monad keadaan, dengan menetapkan lensa secara langsung sebagai homomorfisma monad tersebut.
Jika benar-benar repot untuk memberikan tipe untuk lensanya, mereka akan memiliki tipe peringkat-2 seperti:
newtype Lens s t = Lens (forall a. State t a -> State s a)
Akibatnya, saya lebih suka tidak menyukai pendekatan ini, karena itu dengan sia-sia menarik Anda keluar dari Haskell 98 (jika Anda ingin tipe yang disediakan untuk lensa Anda secara abstrak) dan membuat Anda kehilangan Category
instance untuk lensa, yang akan membuat Anda buat mereka dengan .
. Implementasi juga membutuhkan kelas tipe multi-parameter.
Catatan, semua perpustakaan lensa lain yang disebutkan di sini menyediakan beberapa kombinator atau dapat digunakan untuk memberikan efek fokalisasi keadaan yang sama ini, jadi tidak ada yang diperoleh dengan menyandikan lensa Anda secara langsung dengan cara ini.
Selain itu, kondisi-sisi yang dinyatakan di awal tidak benar-benar memiliki ekspresi yang bagus dalam formulir ini. Seperti halnya 'fclabels', ini menyediakan metode template-haskell untuk secara otomatis menghasilkan lensa untuk jenis rekaman langsung dalam paket utama.
Karena kurangnya Category
instans, baroque encoding, dan persyaratan template-haskell dalam paket utama, ini adalah implementasi yang paling tidak saya sukai.
lensa data
[Sunting: Pada 1.8.0, ini telah berpindah dari paket comonad-transformer ke lensa-data]
data-lens
Paket saya menyediakan lensa dalam hal comonad Store .
newtype Lens a b = Lens (a -> Store b a)
dimana
data Store b a = Store (b -> a) b
Diperluas ini setara dengan
newtype Lens a b = Lens (a -> (b, b -> a))
Anda dapat melihat ini sebagai memfaktorkan argumen umum dari pengambil dan penyetel untuk mengembalikan pasangan yang terdiri dari hasil pengambilan elemen, dan penyetel untuk mengembalikan nilai baru. Ini menawarkan manfaat komputasi bahwa 'penyetel' di sini dapat mendaur ulang beberapa pekerjaan yang digunakan untuk mendapatkan nilai, membuat operasi 'modifikasi' yang lebih efisien daripada dalam fclabels
definisi, terutama ketika accessors dirantai.
Ada juga justifikasi teoretis yang bagus untuk representasi ini, karena subset dari nilai-nilai 'Lens' yang memenuhi 3 hukum yang dinyatakan di awal respons ini adalah lensa-lensa yang fungsi yang dibungkusnya adalah 'comonad coalgebra' untuk comonad store . Ini mengubah 3 hukum berbulu untuk lensa l
menjadi 2 setara dengan pointfree:
extract . l = id
duplicate . l = fmap l . l
Pendekatan ini pertama kali dicatat dan dijelaskan dalam Russell O'Connor Functor
adalah untuk Lens
sebagai Applicative
adalah untuk Biplate
: Memperkenalkan Multiplate dan blog tentang didasarkan pada preprint oleh Jeremy Gibbons.
Ini juga mencakup sejumlah kombinator untuk bekerja dengan lensa secara ketat dan beberapa lensa stok untuk wadah, seperti Data.Map
.
Jadi lensa dalam data-lens
bentuk Category
(tidak seperti lenses
paket), adalah Haskell 98 (tidak seperti fclabels
/ lenses
), waras (tidak seperti bagian belakang data-accessor
) dan memberikan implementasi yang sedikit lebih efisien, data-lens-fd
menyediakan fungsionalitas untuk bekerja dengan MonadState bagi mereka yang mau melangkah keluar dari Haskell 98, dan mesin templat-haskell sekarang tersedia melalui data-lens-template
.
Pembaruan 6/28/2012: Strategi Implementasi Lensa Lainnya
Lensa Isomorfisme
Ada dua pengkodean lensa lain yang patut dipertimbangkan. Yang pertama memberikan cara teoretis yang bagus untuk melihat lensa sebagai cara untuk memecah struktur menjadi nilai bidang, dan 'segalanya'.
Diberi jenis untuk isomorfisma
data Iso a b = Iso { hither :: a -> b, yon :: b -> a }
sedemikian rupa sehingga anggota yang sah memenuhi hither . yon = id
, danyon . hither = id
Kami dapat mewakili lensa dengan:
data Lens a b = forall c. Lens (Iso a (b,c))
Ini terutama berguna sebagai cara untuk memikirkan arti lensa, dan kita dapat menggunakannya sebagai alat penalaran untuk menjelaskan lensa lain.
Lensa van Laarhoven
Kita dapat memodelkan lensa sedemikian rupa sehingga dapat dikomposisi dengan (.)
dan id
, bahkan tanpa menggunakan Category
instance
type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a
sebagai jenis untuk lensa kami.
Maka mendefinisikan lensa semudah:
_2 f (a,b) = (,) a <$> f b
dan Anda dapat memvalidasi sendiri bahwa komposisi fungsi adalah komposisi lensa.
Saya baru-baru ini menulis tentang bagaimana Anda bisa lebih jauh menggeneralisasikan lensa van Laarhoven untuk mendapatkan keluarga lensa yang dapat mengubah jenis bidang, hanya dengan menggeneralisasi tanda tangan ini ke
type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b
Ini memang memiliki konsekuensi yang disayangkan bahwa cara terbaik untuk berbicara tentang lensa adalah dengan menggunakan polimorfisme peringkat 2, tetapi Anda tidak perlu menggunakan tanda tangan itu secara langsung ketika menentukan lensa.
The Lens
I yang didefinisikan di atas untuk _2
sebenarnya adalah LensFamily
.
_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)
Saya telah menulis perpustakaan yang mencakup lensa, keluarga lensa, dan generalisasi lainnya termasuk getter, setter, lipatan dan traversal. Ini tersedia di hackage sebagai lens
paket.
Sekali lagi, keuntungan besar dari pendekatan ini adalah bahwa pengelola perpustakaan dapat benar-benar membuat lensa dengan gaya ini di perpustakaan Anda tanpa menimbulkan ketergantungan perpustakaan lensa apa pun, hanya dengan memasok fungsi dengan tipe Functor f => (b -> f b) -> a -> f a
, untuk tipe khusus mereka 'a' dan 'b'. Ini sangat menurunkan biaya adopsi.
Karena Anda tidak perlu benar-benar menggunakan paket untuk menentukan lensa baru, dibutuhkan banyak tekanan dari kekhawatiran saya sebelumnya tentang menjaga perpustakaan Haskell 98.
lens
paket memiliki fungsionalitas dan dokumentasi terkaya, jadi jika Anda tidak keberatan dengan kerumitan dan ketergantungannya, itulah cara yang harus dilakukan.