是否可以让复合镜头束缚?

时间:2018-10-07 09:52:05

标签: haskell functor lens unification

我无法通过帮助类型检查器来了解以下内容是否可行,或者根本就不可能。 设置有些随意,我只需要一些带有镜头的嵌套数据类型,这里称为ABC

我的问题是,如果我立即使用复合镜头(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

我尝试添加类型签名,并明确量化了fFunctor约束,但到目前为止没有成功。

1 个答案:

答案 0 :(得分:6)

简化很多事情的经验法则是,除非您确实需要光学多态性,否则不要将Lens(或Lens'Setter等作为函数参数,而是采用ALens(或ALens'ASetter)版本,这样可以避免Rank-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'作为参数,则局部绑定会自动采用单态类型:like

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