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

时间:2016-10-27 02:06:21

标签: haskell lens

我正在使用 lens 中的Select Product_ID, sum(Unit_Price * On_Hand) as Product_Value From Product_T Group by Product_ID Having sum(Unit_Price * On_Hand) >= 400.00 Order by sum(Unit_Price * On_Hand) desc; 生成为各种结构重载的字段。我想在具有多个结构的一个字段中使用这些字段,同时必须说明我只想使用一次哪个字段。它看起来像这样:

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) 哪种类型?我试过让GHC推断出类型,但那次失败了。

1 个答案:

答案 0 :(得分:1)

要决定要做什么,我们需要知道您的(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)
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)

然而,实现这一点需要对 HasXHasY等进行抽象。事实上,由于{{1}这样做,这样做有点可行。 }扩展,如Could we abstract over type classes?中所示,其中包含:

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

确定中间类型的第一个代理是必要的,除非您放弃这一点通用性并将GHCi> q (Proxy :: Proxy String) (Proxy :: Proxy HasX) x testB testA 'f' 替换为a。此外,您必须通过将getter作为参数和第二个代理传递两次来指定字段。我完全不相信第二种解决方案值得给我带来麻烦 - 必须定义Stringqx等额外的样板看起来比这里所涉及的所有迂回都要痛苦得多。不过,如果你们这些正在阅读这篇文章的人想提出改进意见,那我就听见了。