如何在同一函数中使用具有不同类型的任意 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)
然而,实现这一目标需要对 classesHasX
、HasY
等进行抽象。实际上,这样做是,由于 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 作为参数传递,另一次是通过第二个代理。我完全不相信第二种解决方案值得麻烦——必须定义 qx
、qy
等的额外样板看起来比这里涉及的所有迂回要容易得多。尽管如此,如果正在阅读本文的任何人想要提出改进建议,我会洗耳恭听。
我正在使用 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)
然而,实现这一目标需要对 classesHasX
、HasY
等进行抽象。实际上,这样做是,由于 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 作为参数传递,另一次是通过第二个代理。我完全不相信第二种解决方案值得麻烦——必须定义 qx
、qy
等的额外样板看起来比这里涉及的所有迂回要容易得多。尽管如此,如果正在阅读本文的任何人想要提出改进建议,我会洗耳恭听。