lensa, fclabels, data-accessor - perpustakaan mana untuk akses struktur dan mutasi yang lebih baik

173

Setidaknya ada tiga perpustakaan populer untuk mengakses dan memanipulasi bidang catatan. Yang saya tahu adalah: data-accessor, fclabels dan lensa.

Secara pribadi saya mulai dengan pengakses data dan saya menggunakannya sekarang. Namun baru-baru ini di haskell-cafe ada pendapat bahwa fclabels lebih unggul.

Karena itu saya tertarik untuk membandingkan ketiga perpustakaan tersebut (dan mungkin lebih).

Tener
sumber
3
Sampai hari ini, lenspaket memiliki fungsionalitas dan dokumentasi terkaya, jadi jika Anda tidak keberatan dengan kerumitan dan ketergantungannya, itulah cara yang harus dilakukan.
modular

Jawaban:

200

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 :-> bdapat langsung diterjemahkan ke jenis di atas. Ini memberikan contoh Kategori(:->) yang berguna karena memungkinkan Anda untuk membuat lensa. Ini juga menyediakan Pointjenis tanpa hukum yang menggeneralisasikan gagasan tentang lensa yang digunakan di sini, dan beberapa pipa ledeng untuk menangani isomorfisma.

Salah satu kendala untuk adopsi fclabelsadalah bahwa paket utama termasuk pipa templat-haskell, sehingga paket tersebut tidak Haskell 98, dan itu juga memerlukan TypeOperatorsekstensi (cukup non-kontroversial) .

pengakses data

[Sunting: data-accessortidak 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 Tyang digunakan untuk mewakili lensa didefinisikan secara internal

newtype T r a = Cons { decons :: a -> r -> (a, r) }

Akibatnya, untuk getnilai 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 Categorycontoh 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 Categoryinstance 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 Categoryinstans, 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-lensPaket 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 fclabelsdefinisi, 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 lmenjadi 2 setara dengan pointfree:

extract . l = id
duplicate . l = fmap l . l

Pendekatan ini pertama kali dicatat dan dijelaskan dalam Russell O'Connor Functoradalah untuk Lenssebagai Applicativeadalah 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-lensbentuk Category(tidak seperti lensespaket), adalah Haskell 98 (tidak seperti fclabels/ lenses), waras (tidak seperti bagian belakang data-accessor) dan memberikan implementasi yang sedikit lebih efisien, data-lens-fdmenyediakan 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 Categoryinstance

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 LensI yang didefinisikan di atas untuk _2sebenarnya 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 lenspaket.

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.

Edward KMETT
sumber
28
Saya suka fclabels karena pendekatannya yang optimis:->
Tener
3
Artikel Inessential Guide to data-accessor dan Inessential guide to fclabels mungkin patut diperhatikan
hvr
10
Apakah menjadi Haskell 1998 kompatibel penting? Karena itu membuat pengembangan kompiler lebih mudah? Dan bukankah kita harus beralih ke berbicara tentang Haskell 2010 saja?
yairchu
55
Oh tidak! Saya adalah penulis asli data-accessor, dan kemudian saya menyerahkannya kepada Henning dan berhenti memperhatikan. The a -> r -> (a,r)representasi juga membuat saya tidak nyaman, dan implementasi asli saya adalah seperti Anda Lensjenis. Heeennnninngg !!
luqui
5
Yairchu: sebagian besar sehingga perpustakaan Anda mungkin memiliki kesempatan untuk bekerja dengan kompiler selain ghc. Tidak ada orang lain yang memiliki templat Haskell. 2010 tidak menambahkan sesuatu yang relevan di sini.
Edward KMETT