如何在同一函数中使用具有不同类型的任意 makeFields 镜头参数?

How to use an arbitrary makeFields lens argument with different types in the same function?

我正在使用 lens 中的 makeFields 来生成为各种结构超载的字段。我想将这些字段与多个结构一起使用,同时必须说明我只想使用哪个字段一次。它看起来像这样:

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}

import Control.Lens

data A = A
    { _aX :: String
    , _aY :: String
    }
makeFields ''A

data B = B
     { _bX :: String -> Char
     , _bY :: String -> Bool
     }
makeFields ''B

-- x can get _aX from an A and _bX from a B

a :: A
a = undefined

b :: B
b = undefined


q :: (Getter A String) AND (Getter B (String -> a)) -> a
q lens = (b^.lens) (a^.lens)

我应该给哪种类型q?我尝试让 GHC 推断类型,但失败了。

要决定要做什么,我们需要知道您的(makeField-生成的)字段的类型是:

GHCi> :t x
x :: (HasX s a, Functor f) => (a -> f a) -> s -> f s

所以涵盖所有 x 类型的抽象(我在注意到你使用 makeFields 之前抱怨的抽象)是一个多参数类型 class HasX,其他字段也类似。这足以让我们在单个实现中使用不同类型的 x

-- Additional extension required: FlexibleContexts
-- Note that GHC is able to infer this type.
qx :: (HasX t (a -> b), HasX s a) => t -> s -> b
qx t s = (t ^. x) (s ^. x)
GHCi> import Data.Maybe
GHCi> let testA = A "foo" "bar"
GHCi> let testB = B (fromMaybe 'ø' . listToMaybe) null
GHCi> qx testB testA
'f'

然而,这并不是您所要求的。你想要这样的东西:

q xOrY b a = (b^.xOrY) (a^.xOrY)

然而,实现这一目标需要对 classesHasXHasY 等进行抽象。实际上,这样做是,由于 ConstraintKinds 扩展,有些可行,如 Could we abstract over type classes? 所示:

-- Additional extensions required: ConstraintKinds, ScopedTypeVariables
-- Additional import required: Data.Proxy
-- GHC cannot infer this type.
q :: forall h s t a b. (h t (a -> b), h s a) => Proxy a -> Proxy h
  -> (forall u c. h u c => Getting c u c) -> t -> s -> b
q _ _ l t s =
    (t ^. (l :: Getting (a -> b) t (a -> b))) (s ^. (l :: Getting a s a))
GHCi> q (Proxy :: Proxy String) (Proxy :: Proxy HasX) x testB testA
'f'

第一个代理,确定中间类型,是必须的,除非你放弃这一点通用性,用String代替a。此外,您必须两次指定该字段,一次是通过将 getter 作为参数传递,另一次是通过第二个代理。我完全不相信第二种解决方案值得麻烦——必须定义 qxqy 等的额外样板看起来比这里涉及的所有迂回要容易得多。尽管如此,如果正在阅读本文的任何人想要提出改进建议,我会洗耳恭听。