Как линзировать поле записи, которая является полиморфной функцией?

Я только что установил библиотеку lens, поэтому я могу легко set использовать вложенную структуру данных. Однако я столкнулся с проблемой. Вот минимальный пример, демонстрирующий мою проблему

Следующий код не компилируется:

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens    

data MyRecord = MyRecord 
  { _func :: forall . a -> a
  }

makeLenses ''MyRecord

changeMyRecord :: MyRecord -> MyRecord
changeMyRecord r = r & func .~ id

Ошибка No Instance for (Contravariant Identity) arising from use of 'func'.

Я просмотрел Contravariant и почти уверен, что мне невозможно создать этот экземпляр, так как

class Contravariant f where
  contramap :: (a -> b) -> f b -> f a

т. е. если f = \x -> x я не вижу, где я могу найти что-то типа a для применения к аргументу функции (a-> b)

Есть ли другой способ изменить MyRecord с помощью линз? Или я мог бы как-то избежать RankNTypes, но все же обойти полиморфный _func в своей записи? Или что-то другое?

Синтаксис обновления записи неуместен — представьте, что MyRecord глубоко вложен.

Пожалуйста, предполагайте, что вы очень мало знаете Haskell при ответе, в частности, я только сегодня начал просматривать библиотеку объективов.


person ocstat    schedule 23.08.2018    source источник


Ответы (1)


lens тут халтурит - нельзя было бы использовать func в качестве линзы (или другой записывающей оптики) с типом

func :: Lens' MyRecord (a -> a)

потому что это означало бы, что вы можете поместить любую эндофункцию конкретного типа, например

changeMyRecord :: MyRecord -> MyRecord
changeMyRecord r = r & func .~ ((+1) :: Int -> Int)

Вместо этого он делает func всего лишь добытчиком

func :: Getter' MyRecord (a -> a)

...и это нормально, поскольку универсальную полиморфную функцию можно использовать для любого типа, поэтому работает следующее:

useMyRecord :: MyRecord -> String
useMyRecord r = show (r^.func $ 1 :: Int)

И видя это

type Getter s a = ∀ f. (Contravariant f, Functor f) => (a -> f a) -> s -> f s

вот откуда это ограничение Contravariant. Сообщение об ошибке No Instance for Contravariant является просто VanLaarhoven-Kmett-ish для Can't use a ‘Getter’ as a ‘Setter’.

То, что вы на самом деле хотели бы иметь, это, конечно,

func :: Lens' MyRecord (∀ a . a -> a)

но это, к сожалению, непредикативный тип, который Haskell не поддерживает. А именно, он расширится до

func :: ∀ f . Functor f => ((∀ a . a -> a) -> f (∀ a . a -> a)) -> MyRecord -> f MyRecord

Обратите внимание, что внутри f есть .

Чтобы получить семантику такой линзы полиморфного поля, вам нужно обернуть ее в тип Rank-0:

newtype PolyEndo = PolyEndo { getPolyEndo :: ∀ a . a -> a }

data MyRecord = MyRecord 
  { _func :: PolyEndo
  }

makeLenses ''MyRecord
-- func :: Lens' MyRecord PolyEndo
person leftaroundabout    schedule 23.08.2018