Дешифриране на съобщения за грешка на обектива, част 1

Уважаемата библиотека lens на Haskell е, по мое мнение, една от неговите предимства като език. Това дава възможност за достъп, модифициране и запитване към дълбоко вложени структури от данни с ефективност, която надхвърля foo.bar.baz = “qux” пунктирания достъп до свойство в OOP стил и направо в нирваната на програмирането.

Да се ​​научиш да използваш lens обаче може да бъде малко трудна битка — въпреки че има много страхотни въвеждащи уроци (1, 2, 3), а авторът на библиотеката дори даде грандиозен ( и невероятно достъпни) говорим за библиотеката, всички тези ресурси се фокусират върху това какво да правим, когато всичко работи добре. Това е добре, но, особено като начинаещ, програмирането се състои много по-често от неща, които вървят погрешно. Lens по-специално прави това много по-трудно за справяне, тъй като поради техниката, която използва, за да накара всичко да се композира добре, съобщенията за грешка, които GHC дава, са склонни да оставят какво да се желае. Тази публикация е първата в продължаваща серия за дешифриране и декодиране на съобщенията за грешки, които GHC ни дава, когато лещите се използват неправилно.

Този обектив не работи тук!

За да започнем, нека да разгледаме вероятно най-простия пример - какво се случва, когато се опитаме да използваме обектив за тип данни, с който той не работи?

Например, да кажем, че имаме следния код:

Тъй като сме използвали макросите haskell на шаблона makeFields за дефиниране на нашите лещи за типовете данни Galaxy и Human (вместо makeLenses или дефинирането им ръчно като лещи от най-високо ниво), това всъщност изглежда доста добре. Ако се опитаме да получим достъп до полето species на Human например, получаваме това:

Това е така, защото макросът makeFields дефинира типове класове за лещите, които дефинира, под формата HasThing - т.е. за нашето поле за име (което се споделя както от Human, така и от Galaxy), ще получим нещо като:

Двата параметъра към HasName typeclass позволяват различни типове данни да имат поле name с различни типове, което е хубаво.

Ами ако не искаме типови класове за нашите полета на лещи обаче? Или какво, ако ние (много разумно) не искаме да използваме шаблон haskell и вместо това напишем всички лещи на ръка? Например полето nickname от типа Animal се генерира от makeLenses, а не от makeFields, което ни дава нещо като:

Ако сега се опитаме да използваме полето nickname на човек, получаваме следното, по-объркващо съобщение за грешка:

Въпреки че първият ред на съобщението за грешка е доста ясен, яснотата върви надолу доста бързо - какво е Getting? И защо OrganismName се появява преди Animal във функцията след него?

Без да навлизам в твърде много подробности (бих препоръчал да прочетете страницата за извеждане на лещи или дори публикацията в блога, която първоначално е изобретила начина, по който работят лещите в момента, ако се интересувате повече), това се дължи на начина, по който лещите библиотеката използва трик с функтори, за да позволи вида на композицията, която очакваме от библиотеката на лещите. Той кодира (мономорфни) лещи като:

и след това разменя функтора f, за да получи поведението getter/setter - Const ни дава Getter, а Identity ни дава сетер.

Така че, когато видим следната функция в нашето съобщение за грешка:

Това, което GHC всъщност казва е, че сме доставили Getter от Animal до OrganismName! По принцип всеки път, когато видите следния тип в съобщение за грешка:

можете да предположите, че GHC говори за Getter от source до target! И всеки път, когато видите Getting target source target, можете да го прочетете като „Получаване на target от source “.

Обобщавайки

Надявам се, че тази публикация е помогнала да започнем да разбираме какво се случва под капака на библиотеката с обективи, по-специално с оглед да започнем да разбираме какво точно да правим, когато GHC издаде съобщение за грешка. В следващата публикация планирам да премина от говорене за отделни екземпляри на структури от данни към това как да се справя с колекции от структури от данни.

Елате говорете с нас, ако работата с лещи и Haskell като цяло ви вълнува толкова, колкото мен и другите инженери тук в Urbint.