Каждая линза — это обход… как?

Control.Lens.Tutorial говорит:

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

Обратите внимание, что единственная разница между Lens и Traversal — это ограничение класса типа. Объект Lens имеет ограничение Functor, а Traversal имеет аппликативное ограничение. Это означает, что любая линза автоматически также является допустимым обходом (поскольку Functor является суперклассом Applicative).

Я просто не соблюдаю здесь логическую последовательность. Мы знаем, что каждый Applicative — это Functor. Не следует ли из этого вообще, что каждый Traversal' является Lens'? Однако руководство приходит к обратному выводу.

Вам также может понравиться Почему Num может действовать как дробный?.

Daniel Wagner 23.02.2024 22:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
1
745
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Lens' работает для всех функторов, включая все аппликативные функторы, поэтому, если функция g является Lens', то g также является Traversal'.

Traversal' работает для всех аппликативных функторов, но не обязательно для всех функторов. Таким образом, если функция h является Traversal', это не обязательно Lens'.

(Я не тот, кто описывает это формально, но, по крайней мере, мне кажется, что типы «контравариантны» в своих ограничениях. Lens' является подтипом Traversal' именно потому, что Functor является суперклассом Applicative.)

Другой способ выразить это, который может помочь: если бы линза была функтором, а обход был аппликативом, логика в вопросе имела бы смысл. Скорее, линза — это нечто, что принимает в качестве аргумента функтор, а обход — аппликатив. Таким образом, более общий пункт — это тот, который имеет более общий аргумент.

amalloy 21.02.2024 21:34

Честно говоря, мне бы очень хотелось, чтобы кто-нибудь (точно) описал, как работает forall f. Я предполагаю, что хороший ответ объяснил бы разницу между Applicative f => ... и forall f . Applicative f => .... (Я прав, полагая, что это необходимый forall, а не просто явный forall, верно?)

chepner 21.02.2024 21:41

(Либо в новом ответе, либо в редактировании этого; в последнем случае я согласен изменить это на вики сообщества.)

chepner 21.02.2024 21:42

Я не уверен, откуда вы взяли имена f и g (вероятно, просто использовали имена общих функций?), но не сразу понятно, что вы не ссылаетесь на переменную типа f из определений в вопросе.

Bergi 23.02.2024 00:32

@chepner Возможно, вам понравится мой ответ в Почему число может действовать как дробное?, который пытается точно описать, как forall работает и как он взаимодействует с классами типов.

Daniel Wagner 23.02.2024 22:52

Обычно я не сторонник милых аналогий с «существами из реального мира», но этот вопрос, вероятно, станет яснее, если рассмотреть следующий пример:

class Animal a where feed :: a -> ArmouredGlove -> IO String

instance Animal Cat where feed _ _ = print "Miaow"
instance Animal Dog where feed _ _ = print "Woof"
instance Animal Lion where feed _ _ = print "Rwaaar"

class Animal a => Pet a where cuddle :: a -> UnprotectedHand -> IO String

instance Pet Cat where cuddle _ _ = print "Purr"
instance Pet Dog where cuddle _ _ = print "Yawn"

type PetOwner = ∀ a . Pet a => a -> IO String
type ZooWarden = ∀ a . Animal a => a -> IO String

peter :: PetOwner
peter = \pet -> cuddle pet peter'sHand

zoey :: ZooWarden
zoey = \animal -> feed animal zoey'sGlove

Здесь ZooWarden — это подтип PetOwner. Зои прекрасно справляется с домашним животным, она может кормить любую собаку, а также львов. Но у Питера нет такой квалификации, он может обращаться только с безобидными домашними животными. Таким образом, именно потому, что он ограничен более конкретным классом животных, он сам принадлежит к более общему типу личности, чем специализированный смотритель зоопарка.

Почему здесь присутствуют типы Peter'sHand и Zoey'sGlove? Разве они не должны быть конкретными примерами UnprotectedHand и ArmouredGlove?

OpenAI was the last straw 22.02.2024 18:24

@А.Р. они конструкторы. Но да, возможно, это не имеет особого смысла, я придаю им простые значения.

leftaroundabout 22.02.2024 20:55

Рассмотрим представление классов типов с передачей словаря. Вот как на самом деле реализуются классы типов в GHC.

Фон

В этой форме классы типов, такие как Functor и Applicative, выглядят примерно так:

data Functor f =
  MkFunctor
    { fmap :: (a -> b) -> f a -> f b }

data Applicative f =
  MkApplicative
    { appFunctor :: Functor f
    , pure :: a -> f a
    , (<*>) :: f (a -> b) -> f a -> f b
    }

Экземпляры — это значения этих типов, например так:

listFunctor :: Functor []
listFunctor =
  MkFunctor
    { fmap = map }

Линзы и траверсы

Теперь предположим, что у нас есть линза someLens :: Lens' A B и мы хотим превратить ее в обход. Давайте расширим синоним типа и воспользуемся передачей словаря:

someLens :: Functor f -> (b -> f b) -> (a -> f a)

В этом стиле вы можете использовать его с функтором списка вот так someLens listFunctor.

Нам нужен обход someTraversal :: Traversal' A B. Когда мы реализуем это, нам нужно будет предоставить someLens значение Functor f. Но у нас уже есть значение Applicative f:

someTraversal :: Applicative f -> (b -> f b) -> (a -> f a)
someTraversal applicativeDict = someLens (appFunctor applicativeDict)

Мы можем просто использовать метод доступа к полю, который получает реализацию Functor для предоставленного нам экземпляра Applicative, и у нас есть Traversal. Поле доступа имеет тип appFunctor :: Applicative f -> Functor f.

Другие вопросы по теме