лінзи, fclabel, data-accessor - яка бібліотека для доступу до структури та мутації краще


173

Існує щонайменше три популярні бібліотеки для доступу та маніпулювання полями записів. Мені відомі: доступ до даних, флекти та лінзи.

Особисто я почав із доступу до даних і зараз їх використовую. Однак нещодавно в haskell-cafe виникла думка про те, що fclabels є вищим.

Тому мені цікаво порівняти ці три (а може й більше) бібліотеки.


3
На сьогоднішній день lensпакет має найбагатший функціонал та документацію, тому якщо ви не заперечуєте проти його складності та залежностей, це шлях.
модульний

Відповіді:


200

Є щонайменше 4 бібліотеки, які я знаю, що надають об'єктиви.

Поняття лінзи полягає в тому, що вона забезпечує щось ізоморфне

data Lens a b = Lens (a -> b) (b -> a -> a)

надання двох функцій: геттер і сетер

get (Lens g _) = g
put (Lens _ s) = s

відповідно до трьох законів:

По-перше, якщо ви щось покладете, ви зможете повернути його назад

get l (put l b a) = b 

По-друге, отримання та встановлення не відповідає

put l (get l a) a = a

І по-третє, два рази ставити - це те саме, що ставити один раз, а точніше, виграти другий ставок.

put l b1 (put l b2 a) = put l b1 a

Зауважте, що типової системи недостатньо, щоб перевірити ці закони для вас, тому вам потрібно їх забезпечити самостійно незалежно від того, якою реалізацією об'єктивів ви користуєтесь.

Багато з цих бібліотек також надають купу додаткових комбінаторів зверху, і, як правило, певну форму шаблонного механізму haskell для автоматичного генерування об'єктивів для полів простих типів записів.

Зважаючи на це, ми можемо звернутися до різних реалізацій:

Впровадження

fclabels

fclabels , мабуть, є найбільш легко аргументованим щодо бібліотек об'єктивів, оскільки його a :-> bможна безпосередньо перекласти на вищевказаний тип. Він пропонує примірник категорії,(:->) який корисний, оскільки дозволяє складати лінзи. Він також забезпечує беззаконний Pointтип, який узагальнює поняття використовуваної тут лінзи, а також деяку сантехніку для боротьби з ізоморфізмами.

Одним із перешкод на шляху прийняття fclabelsє те, що основний пакет включає сантехніку шаблону-haskell, тому пакет не є Haskell 98, і він також вимагає (досить не суперечливого) TypeOperatorsрозширення.

дані-доступ

[Редагувати: data-accessorбільше не використовує це представлення, але перейшло до форми, подібної до форми data-lens. Я все ж зберігаю цей коментар.]

data-accessor є дещо популярнішим fclabels, відчасти тому, що це Haskell 98. Однак його вибір внутрішнього представництва змушує мене трохи кинути в рот.

Тип, який Tвін використовує для представлення лінзи, внутрішньо визначається як

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

Отже, для getзначення об’єктива потрібно подати невизначене значення для аргументу "a"! Це вражає мене як неймовірно некрасивою та спеціальною реалізацією.

При цьому Геннінг включив сантехніку шаблону-haskell для автоматичного генерування для вас аксесуарів в окремому пакеті " data-accessor-template ".

Він має перевагу пристойно великого набору пакетів, які вже використовують його, будучи Haskell 98, і надає все важливий Categoryекземпляр, тому якщо ви не звертаєте уваги на те, як виготовлена ​​ковбаса, цей пакет насправді є досить розумним вибором .

лінзи

Далі є пакет лінз , який зазначає, що лінза може забезпечити гомоморфізм стану монади між двома монадами стану, визначаючи лінзи безпосередньо як такі мономорфізми монади.

Якщо б насправді було задумано надати тип лінз, вони мали б тип 2 типу:

newtype Lens s t = Lens (forall a. State t a -> State s a)

Як результат, мені не подобається такий підхід, оскільки він марно позбавляє вас від Haskell 98 (якщо ви хочете, щоб тип надав вашим об'єктивам абстрактно) і позбавляв вас Categoryпримірника лінз, що дозволить вам скласти їх .. Реалізація також вимагає класів типів з декількома параметрами.

Зауважте, всі інші бібліотеки об'єктивів, згадані тут, забезпечують деякий комбінатор або можуть бути використані для надання цього самого ефекту фокалізації стану, тому нічого не можна отримати, кодуючи ваш об'єктив безпосередньо таким чином.

Крім того, зазначені на початку побічні умови насправді не мають приємного вираження в цій формі. Як і у випадку з "fclabels", це дає метод шаблону haskell для автоматичного генерування об'єктивів для типу запису безпосередньо в основному пакеті.

Через відсутність Categoryпримірника, кодування в стилі бароко та вимога шаблону-haskell в основному пакеті, це моя найменш улюблена реалізація.

даних-об'єктив

[Редагувати: Станом на 1.8.0 вони перемістилися з пакету комонад-трансформаторів до об'єктива даних]

Мій data-lensпакет пропонує лінзи з точки зору магазину Comonad.

newtype Lens a b = Lens (a -> Store b a)

де

data Store b a = Store (b -> a) b

Розгорнуте це рівнозначно

newtype Lens a b = Lens (a -> (b, b -> a))

Ви можете розглядати це як факторинг загального аргументу від геттера та сеттера для повернення пари, що складається з результату вилучення елемента, та сеттера, щоб повернути нове значення. Це пропонує обчислювальну вигоду, що "сеттер" тут можна переробити частину робіт, які використовуються для виведення значення, зробивши для цього більш ефективну операцію "модифікувати", ніж у fclabelsвизначенні, особливо коли ланцюги приєднань.

Існує також приємне теоретичне обгрунтування цього уявлення, оскільки підмножина значень об'єктива, що відповідає 3 законам, викладеним на початку цієї відповіді, є саме тими лінзами, для яких загорнута функція є «комонадною вугільною когеброю» для комонаду магазину . Це перетворює 3 волохаті закони для лінзи lдо 2 чудово точкових еквівалентів:

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

Цей підхід був вперше відзначений і описаний в Russell О'Коннор Functorє , Lensяк Applicativeце Biplate: Вводячи багатодискове і писав про на основі препринт Джеремі Gibbons.

Він також включає ряд комбінаторів для суворої роботи з лінзами та деякі лінзи для контейнерів, наприклад Data.Map.

Таким чином, лінзи у data-lensформі Category(на відміну від lensesпакета) - Haskell 98 (на відміну від fclabels/ lenses), є здоровими (на відміну від заднього кінця data-accessor) та забезпечують дещо ефективнішу реалізацію, data-lens-fdзабезпечують функціональність для роботи з MonadState для тих, хто хоче вийти назовні від Haskell 98, і техніка шаблону haskell тепер доступна через data-lens-template.

Оновлення 28.06.2012: Інші стратегії впровадження об'єктива

Лінзи ізоморфізму

Є ще два кодування об'єктива, які варто врахувати. Перший дає приємний теоретичний спосіб розглянути лінзу як спосіб розбити структуру на значення поля та "все інше".

Дано тип ізоморфізмів

data Iso a b = Iso { hither :: a -> b, yon :: b -> a }

таким, що дійсні члени задовольняють hither . yon = id, таyon . hither = id

Ми можемо представити лінзу за допомогою:

data Lens a b = forall c. Lens (Iso a (b,c))

Вони в першу чергу корисні як спосіб подумати про значення лінз, і ми можемо використовувати їх як інструмент міркування для пояснення інших лінз.

лінзи ван Лаарховена

Ми можемо моделювати лінзи таким чином, що вони можуть бути складені з (.)і idнавіть без Categoryнаприклад , з використанням

type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a

як тип для наших лінз.

Тоді визначити об'єктив так само просто, як:

_2 f (a,b) = (,) a <$> f b

і ви можете переконатись, що композиція функції - це лінзова композиція.

Нещодавно я писав про те, як можна додатково узагальнити лінзи ван Лаарховена, щоб отримати сімейства лінз, які можуть змінювати типи полів, просто узагальнивши цей підпис до

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

Це має невдалий наслідок того, що найкращий спосіб говорити про лінзи - це використовувати поліморфізм 2-го рангу, однак вам не потрібно використовувати цю підпис безпосередньо при визначенні лінз.

LensЯ визначив вище _2насправді LensFamily.

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

Я написав бібліотеку, яка включає лінзи, сімейство лінз та інші узагальнення, включаючи геттери, сетери, складки та обходи. Він доступний при злому як lensпакет.

Знову ж таки, велика перевага цього підходу полягає в тому, що технічні працівники бібліотеки можуть фактично створювати об'єктиви в такому стилі у ваших бібліотеках, не несучи ніякої залежності від бібліотеки об'єктивів, просто надаючи функції типу Functor f => (b -> f b) -> a -> f a , для їх конкретних типів 'a' та 'b'. Це значно знижує вартість усиновлення.

Оскільки вам не потрібно використовувати пакет для визначення нових лінз, це потребує великого тиску з моїх попередніх проблем щодо збереження бібліотеки Haskell 98.


28
Мені подобаються fclabels за його оптимістичний підхід:->
Tener


10
Чи важливим є сумісність Haskell 1998? Тому що це спрощує розробку компілятора? І чи не слід переходити до розмови про Haskell 2010?
yairchu

55
О ні! Я був оригінальним автором data-accessor, а потім передав це Геннінгу і перестав звертати увагу. a -> r -> (a,r)Подання також робить мене незручно, і моє початкове впровадження було так само , як ваш Lensтип. Heeennnninngg !!
luqui

5
Yairchu: це здебільшого, щоб ваша бібліотека могла мати шанс працювати з компілятором, відмінним від ghc. Ніхто інший не має шаблону Haskell. 2010 рік тут не додає нічого актуального.
Едвард KMETT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.