Линзы 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.