Линзы Haskell: как сделать игру красивой с траверсом?
Я пытаюсь узнать о линзах, внедряя их в Haskell. Я реализовал комбинатор view
следующим образом:
{-# LANGUAGE RankNTypes #-}
import Control.Applicative
import Data.Traversable
type Lens s a = Functor f => (a -> f a) -> s -> f s
view :: Lens s a -> s -> a
view lens = getConst . lens Const
Однако, когда я пытаюсь использовать его в сочетании с traverse
, я получаю следующее сообщение об ошибке:
Prelude> :load Lens.hs
[1 of 1] Compiling Main ( Lens.hs, interpreted )
Ok, modules loaded: Main.
*Main> :t view traverse
<interactive>:1:6:
Could not deduce (Applicative f) arising from a use of ‘traverse’
from the context (Traversable t)
bound by the inferred type of it :: Traversable t => t a -> a
at Top level
or from (Functor f)
bound by a type expected by the context:
Functor f => (a -> f a) -> t a -> f (t a)
at <interactive>:1:1-13
Possible fix:
add (Applicative f) to the context of
a type expected by the context:
Functor f => (a -> f a) -> t a -> f (t a)
or the inferred type of it :: Traversable t => t a -> a
In the first argument of ‘view’, namely ‘traverse’
In the expression: view traverse
К сожалению, я не понимаю это сообщение об ошибке. Пожалуйста, объясните, что это значит и как я могу это исправить.
Ответы
Ответ 1
Как уже объясняют другие ответы, проблема в том, что view
ожидает что-то, что работает для любого Functor f
, но traverse
работает только в том случае, если f
также Applicative
(и есть функторы, которые не являются аппликативны).
В lens
проблема решена, если тип view
не принимает аргумент Rank2
(фактически, большинство функций в объективе не используют синоним типа Lens, они всегда используют что-то более слабое), Для вашей функции обратите внимание, что view
использует только f ~ Const
. Вот почему вы можете изменить подпись типа:
view :: ((a -> Const a a) -> s -> Const a s) -> s -> a
Реализация может оставаться неизменной, но теперь view
также работает на traverse
:
view traverse :: (Traversable t, Monoid a) => t a -> a
Обратите внимание на дополнительное ограничение Monoid
. Это ограничение появляется, потому что если вы установите f ~ Const a
в traverse :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
, вам понадобится экземпляр Applicative (Const a)
. Однако этот экземпляр имеет ограничение Monoid
на a
. И это также имеет смысл, потому что пройденный может быть пустым или содержать более одного элемента, поэтому вам нужны mempty
и mappend
.
Ответ 2
traverse
имеет этот тип:
traverse :: (Applicative f, Traversable t) => (x -> f y) -> t x -> f (t y)
Если мы сделаем свободную переменную f
в определении типа Lens
явным, ее определение на самом деле
type Lens s a = forall f . Functor f => (a -> f a) -> s -> f s
Итак, view
хочет функцию, которая может работать на любом Functor
, тогда как traverse
может работать только на Applicative
.
Вы можете исправить ошибку, просто изменив Functor
на Applicative
в определении Lens
, но я не уверен, что именно это вы хотели бы достичь здесь.
Ответ 3
tl; dr - Согласно вашему определению Lens
, traverse
не может быть Lens
, потому что traverse
не работает для всех Functor
s.
Посмотрите на свои типы:
λ :set -XRankNTypes
λ :m +Control.Applicative Data.Traversable
λ type Lens s a = Functor f => (a -> f a) -> s -> f s
λ :t traverse
traverse
:: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
Теперь в этой точке мы видим, что traverse
является, в некотором роде, немного более общим, чем наш тип Lens
, - он может принимать функцию
от a -> f b
, где наша линза может принимать только функции от a -> f a
.
Ограничение этого случая не является проблемой, поэтому мы можем сказать
λ :t traverse :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
traverse :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
:: (Applicative f, Traversable t) => (a -> f a) -> t a -> f (t a)
Итак, теперь очевидно, что если traverse
должно быть Lens
, оно должно быть Lens (t a) a
, так как это единственный способ сделать линейные переменные строк.
Итак, давайте попробуем это.
λ :t traverse :: Lens (t a) a
<interactive>:1:1:
Could not deduce (Traversable t1) arising from a use of `traverse'
from the context (Functor f)
bound by the inferred type of
it :: Functor f => (a -> f a) -> t a -> f (t a)
at Top level
or from (Functor f1)
bound by an expression type signature:
Functor f1 => (a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
at <interactive>:1:1-24
Possible fix:
add (Traversable t1) to the context of
an expression type signature:
Functor f1 => (a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
or the inferred type of
it :: Functor f => (a -> f a) -> t a -> f (t a)
In the expression: traverse :: Lens (t a) a
Oof, это не понравилось. О, подождите, чтобы использовать traverse
наш тип t
должен быть Traversable
, поэтому добавьте это ограничение. (Также как "Возможное исправление" ) предлагает:
λ :t traverse :: Traversable t => Lens (t a) a
<interactive>:1:1:
Could not deduce (Applicative f1) arising from a use of `traverse'
from the context (Functor f, Traversable t)
bound by the inferred type of
it :: (Functor f, Traversable t) => (a -> f a) -> t a -> f (t a)
at Top level
or from (Traversable t1, Functor f1)
bound by an expression type signature:
(Traversable t1, Functor f1) =>
(a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
at <interactive>:1:1-41
Possible fix:
add (Applicative f1) to the context of
an expression type signature:
(Traversable t1, Functor f1) =>
(a1 -> f1 a1) -> t1 a1 -> f1 (t1 a1)
or the inferred type of
it :: (Functor f, Traversable t) => (a -> f a) -> t a -> f (t a)
In the expression: traverse :: Traversable t => Lens (t a) a
Итак, теперь проблема заключается в том, что он не может сделать вывод о том, что f
является Applicative
(что также необходимо использовать traverse
), просто что он Functor
(который он получает из определения Lens
).
Мы не можем добавить Applicative f
в контекст, хотя - f
скрыт. Когда мы говорим type Lens s a = Functor f => (a -> f a) -> s -> f s
, мы говорим, что Lens
должен работать для всех Functor
s.
Но traverse
работает только для подмножества Functor
, которые также Applicative
. Таким образом, тип traverse
более конкретный, чем разрешен для Lens
es.