【问题标题】:Is it impossible to let-bind a composite lens?复合镜头不能装订吗?
【发布时间】:2018-10-07 09:52:04
【问题描述】:

我无法通过帮助类型检查器来理解以下是否可行,或者完全不可能。 设置有点随意,我只需要一些带有镜头的嵌套数据类型,这里称为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 约束,但到目前为止没有成功。

【问题讨论】:

    标签: haskell functor haskell-lens unification


    【解决方案1】:

    简化许多事情的经验法则是不要将Lens(或Lens',或Setter 等)作为函数参数,除非你真的需要它的光学多态性,而是取而代之的是@987654324 @(或ALens'ASetter)版本,避免了Rank-2多态问题。

    问题是如果你在do 块中给compositeLens 一个名字,那么它必须有一个不能再从它的上下文中推断出来的类型。但是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
    

    【讨论】:

    • 感谢您的详尽回答!由于你提到的原因,我有一种预感doesNotWork2 失败了,我不知道A... 家族。对于我的大多数用例,我可能不需要 rank-2 多态性,所以这是完美的!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多