Какова будет сигнатура типа функции, которую можно применять как (& f %~), так и (^. f), где операторы взяты из библиотеки Lens?

У меня есть функция, которая получает значение из записи и устанавливает его в то же поле в другой записи.

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

setToPercent perc basedOn toSetOn = setStat
    where baseStat = basedOn ^. _func -- type is Maybe Float
          percentDelta = (\a -> perc * a) <$> baseStat -- Maybe Float
          setStat = toSetOn & _func %~ (\a -> (+) <$> a <*> percentDelta)

Где _func определяется как MyDataType -> Maybe Float (функция по умолчанию из синтаксиса записи.)

И это работает именно так, как я ожидаю. Что я хочу знать, так это сигнатуру типа _func.

Я знаю, что здесь basedOn ^. _func, _func будет тип Getting (f0 Float) MyDataType (f0 Float)

и здесь toSetOn & _func %~ (\a -> (+) <$> a <*> percentDelta, _func будут иметь тип ASetter Weapon Weapon (f0 Float) (f0 Float), где данные Weapon и MyDataType в основном имеют один и тот же тип данных с идентичными конструкторами.

Но я не уверен, что это будет общий тип. Например, каким был бы тип функции, если бы я обобщил setToPercent по отношению к _func, как в

setToPercent fnToApply perc basedOn toSetOn = setStat
    where baseStat = basedOn ^. fnToApply -- type is Maybe Float
          percentDelta = (\a -> perc * a) <$> baseStat -- Maybe Float
          setStat = toSetOn & fnToApply %~ (\a -> (+) <$> a <*> percentDelta)

person atis    schedule 09.01.2019    source источник


Ответы (1)


Интересный вопрос!

Я знаю, что здесь basedOn ^. _func, _func будет тип Getting (f0 Float) MyDataType (f0 Float)

и здесь toSetOn & _func %~ (\a -> (+) <$> a <*> percentDelta, _func будут иметь тип ASetter Weapon Weapon (f0 Float) (f0 Float)

Таким образом, неофициально _func является и геттером, и сеттером. Глядя на диаграмму на главной странице Lens на Hackage или на сообщение в блоге Glassery Олега Гренруса, наименее общего предка геттеров и сеттеров линз.

Библиотека линз предлагает как минимум два представления линз: полиморфное Lens' Weapon (f0 Float) (определение содержит forall) и мономорфное ALens' Weapon (f0 Float) (заменяет forall конкретным функтором).

Поскольку здесь мы ожидаем, что _func будет автоматически специализироваться на ASetter и Getting, одним из простых способов является использование полиморфного (для этого требуется RankNTypes, если _func является параметром, а не привязкой верхнего уровня).

_func :: Lens' Weapon (f0 Float)   -- Polymorphic
      -- specializes to
      :: Getting (f0 Float) Weapon (f0 Float)
      :: ASetter' Weapon (f0 Float)

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

_func :: ALens' Weapon (f0 Float)   -- Monomorphic

cloneLens _func :: Lens' Weapon (f0 Float)  -- Polymorphic again

-- The ALens' type synonym usually appears in argument position
myFun :: ALens' Foo Bar -> MyResult
myFun _func = ...

-- Example usage
basedOn ^. cloneLens _func
toSetOn & cloneLens _func %~ ...
person Li-yao Xia    schedule 09.01.2019
comment
Спасибо за ответ! Обе версии работают отлично! Также мне пришлось добавить (Functor f0, Applicative f0) в контекст типа, чтобы разрешить использование <$>, <+> и <*>. - person atis; 09.01.2019