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

Haskell 如何在同一函数中使用具有不同类型的任意makeFields lens参数?,haskell,haskell-lens,Haskell,Haskell Lens,我正在使用lens中的makeFields为各种结构生成重载字段。我想在一个具有多个结构的字段中使用这些字段,同时必须声明我只想使用一次的字段。它看起来是这样的: {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE FlexibleInstanc

我正在使用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
之前我一直在抱怨的抽象)是一个多参数类型类
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)
然而,这并不是你所要求的。你想要的是:

q xOrY b a = (b^.xOrY) (a^.xOrY)
然而,实现这一点需要对类
HasX
HasY
等进行抽象。事实上,由于
ConstraintKinds
扩展,这样做在某种程度上是可行的,如下所示:

-- 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))

确定中间类型的第一个代理是必需的,除非您放弃这一点通用性,用
String
替换
a
。此外,您必须两次指定字段,一次是将getter作为参数传递,另一次是通过第二个代理。我一点也不相信第二种解决方案值得费心——必须定义
qx
qy
等额外的样板文件看起来比这里涉及的所有迂回要少一些痛苦。不过,如果你们当中有人想提出改进建议,我洗耳恭听。

(b^.lens)a^.lens
=
((b^.lens)a^)。镜头
但是你可能想要
(b^.lens)(a^.lens)
。但是,类型
(Getter X Y,…)
甚至无效(它是非指示性的),因此您必须使用
ReifiedGetter
或writer
Getter A..->吸气剂B…->
@2426021684为什么需要两个
Getter
s成为同一个镜头?@BenjaminHodgson不一定是这样,但将同一个镜头传递给函数两次似乎很奇怪本杰明·霍奇森的问题是一个更基本的问题
aX
bX
彼此无关,因此您可能不应该期望使用单个参数同时指定它们。在我的应用程序中,一组对象具有相同的属性,并且为函数提供相同的参数2到3次似乎是重复的
-- 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'