Разве нельзя позволить связать композитный объектив? - PullRequest
0 голосов
/ 07 октября 2018

У меня проблемы с пониманием, возможно ли следующее с помощью проверки типов или просто невозможно.Настройка немного произвольна, мне просто нужны некоторые вложенные типы данных с объективами, которые здесь называются A, B, C.

Моя проблема в том, что я могу использовать комбинированный объектив (bLens . a), еслиЯ немедленно вызываю что-то вроде view, используя его, но если я попытаюсь связать его и дать ему имя, я получаю сообщения об ошибках.

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Debug where

import Control.Eff
import Control.Eff.Reader.Strict
import Control.Lens

data A = A

data B = B
  { _a :: A
  }
makeLenses ''B

data C = C
  { _b :: B
  }
makeLenses ''C

askLensed :: ( Member (Reader r) e ) => Lens' r a -> Eff e a
askLensed l = view l <$> ask

works :: ( Member (Reader C) e ) => Lens' C B -> Eff e A
works bLens = do
  askLensed (bLens . a)

doesNotWork :: ( Member (Reader C) e ) => Lens' C B -> Eff e A
doesNotWork bLens = do
  let compositeLens = bLens . a
  askLensed compositeLens

doesNotWork2 :: ( Member (Reader C) e ) => Lens' C B -> Eff e A
doesNotWork2 bLens = do
  let compositeLens :: Lens' C A = bLens . a
  askLensed compositeLens

butThisIsFine :: Lens' C B -> Lens' C A
butThisIsFine bLens =
  let compositeLens = bLens . a in compositeLens

Сообщения об ошибках:

• Could not deduce (Functor f0) arising from a use of ‘bLens’
  from the context: Member (Reader C) e
    bound by the type signature for:
               doesNotWork :: forall (e :: [* -> *]).
                              Member (Reader C) e =>
                              Lens' C B -> Eff e A
    at /home/.../.stack-work/intero/interoW51bOk-TEMP.hs:32:1-62
  The type variable ‘f0’ is ambiguous
  Relevant bindings include
    compositeLens :: (A -> f0 A) -> C -> f0 C

и:

• Couldn't match type ‘f0’ with ‘f’
    because type variable ‘f’ would escape its scope
  This (rigid, skolem) type variable is bound by
    a type expected by the context:
      Lens' C A
    at /home/.../.stack-work/intero/interoW51bOk-TEMP.hs:35:3-25
  Expected type: (A -> f A) -> C -> f C
    Actual type: (A -> f0 A) -> C -> f0 C
• In the first argument of ‘askLensed’, namely ‘compositeLens’
  In a stmt of a 'do' block: askLensed compositeLens
  In the expression:
    do let compositeLens = bLens . a
       askLensed compositeLens
• Relevant bindings include
    compositeLens :: (A -> f0 A) -> C -> f0 C

Я попытался добавить сигнатуры типов с явным количественным определением ограничений f и Functor, но пока безуспешно.

1 Ответ

0 голосов
/ 07 октября 2018

Эмпирическое правило, которое упрощает многие вещи, состоит в том, чтобы не принимать Lens (или Lens', или Setter и т. Д.) В качестве аргументов функции, если вам действительно не нужен оптический полиморфизм, а вместо этого принимать ALens (или ALens' или ASetter) версия, которая устраняет проблемы полиморфизма Ранга 2.

Проблема в том, что если вы дадите compositeLens имя в блоке do, то оно должноиметь тип, который больше не может быть выведен из его контекста.Но Lens' C A - это полиморфный тип под капотом, и это значительно усложняет вывод типа.Это на самом деле нормально, если вы даете явную подпись:

doesActuallyWork2 :: ( Member (Reader C) e ) => Lens' C B -> Eff e A
doesActuallyWork2 bLens = do
  let compositeLens :: Lens' C A
      compositeLens = bLens . a
  askLensed compositeLens

Ваша версия doesNotWork2 не сработала, потому что определение с встроенной подписью перевернуто в RHS,как

doesNotWork2 :: ( Member (Reader C) e ) => Lens' C B -> Eff e A
doesNotWork2 bLens = do
  let compositeLens = bLens . a :: Lens' C A
  askLensed compositeLens

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

Более простое решение состоит в том, чтобыполностью избегайте этого локального полиморфизма, который вам на самом деле не нужен: если вы берете ALens' в качестве аргумента, локальное связывание автоматически принимает мономорфный тип: как

worksEasily :: ( Member (Reader C) e ) => ALens' C B -> Eff e A
worksEasily bLens = do
  let compositeLens = bLens . a
  askLensed compositeLens

На самом деле не совсем так;оказывается, вы не хотите ALens' здесь, но Getting.Самый простой способ найти это - удалить сигнатуру на askLensed и позволить компилятору сообщить вам, что он выводит, а затем работать в обратном направлении.

askLensed :: ( Member (Reader r) e ) => Getting a r a -> Eff e a
askLensed l = view l <$> ask

worksEasily :: ( Member (Reader r) e ) => Getting A r B -> Eff e A
worksEasily bLens = do
  let compositeLens = bLens . a
  askLensed compositeLens
...