您需要一个包含String 名称的容器,然后是不同类型的Values 列表。您使用Bar 的方式仅限于某些类型的Variable。如果你想要一个真正的、不受限制的异构容器,你需要一个 GADT。
data HMapList (f :: k -> Type) (xs :: [k]) :: Type where
HMNil :: HMapList f '[]
HMCons :: f x -> HMapList f xs -> HMapList f (x : xs)
data Container xs = Container {
containerName :: String
, containerValues :: HMapList Variable xs
}
Functor 在这里是不可能的。密切相关的是镜头的概念,你可以得到它。 “正确”地执行此操作需要一些样板:
data Elem (x :: k) (xs :: [k]) where -- where do I find x in xs?
Here :: Elem x (x : xs)
There :: Elem x xs -> Elem x (y : xs)
data SElem (e :: Elem (x :: k) xs) where
SHere :: SElem Here
SThere :: SElem e -> SElem (There e)
-- these are like indices: think 0 = (S)Here, 1 = (S)There (S)Here, 2 = (S)There 1, etc.
type family Replace (xs :: [k]) (e :: Elem x xs) (y :: k) :: [k] where
Replace (_ : xs) Here y = y : xs
Replace (x : xs) (There e) y = x : Replace xs e y
hmLens :: forall x y xs (e :: Elem x xs) f g. Functor g => SElem e ->
-- Lens (f x) (f y) (HMapList f xs) (HMapList f (Replace xs e y))
(f x -> g (f y)) -> HMapList f xs -> g (HMapList f (Replace xs e y))
hmLens SHere mod (HMCons fx xs) = (\fx' -> HMCons fx' xs) <$> mod fx
hmLens (SThere e) mod (HMCons fx xs) = (\xs' -> HMCons fx xs') <$> hmLens e mod xs
hmLens 代表HMapList 的“字段”。您可以使用lens 库中的运算符来操作包含在Container 的“槽”中的f x,并完成类型更改。也就是说,一旦您在列表中选择了带有Elem 的位置,您就可以使用Functory 将as 替换为bs,方法是使用a -> b。不过,Container 本身并不是函子。相反,它生成了一个无限的函子家族,比我更有经验的人可能会说出它们的名字。执行您的示例:
container :: Container [Int, Double, String]
container = Container "container" $ HMCons (Variable "v1" [5,1]) $
HMCons (Variable "v2" [3.2,2,6]) $
HMCons (Variable "v3" ["bob", "dupont"])
HMNil
container' :: Container [Int, Double, String]
container' = let Container name vs = container
in Container name $ vs & (hmLens SHere).mapped %~ (+2)
-- ^ access 1st field ^ modify w/ function
-- ^ flip ($) ^ peek into Variable
-- a proper Lens (Container xs) (Container ys) (HMapList Variable xs) (HMapList Variable ys)
-- would alleviate the match/rebuild pain.
如果您想扩展它以将(+2) 应用于Container 内的所有Variable Ints(可能会更改类型,例如使用show),那么您可以调整@ 的一部分987654321@.
Container 也是一个正确的小写“f”函子。让我定义一类类别:
data ZippingWith (f :: a -> b -> Type) (as :: [a]) (bs :: [b]) where
ZWNil :: ZippingWith f '[] '[]
ZWCons :: f a b -> ZippingWith f as bs -> ZippingWith f (a : as) (b : bs)
如果f :: k -> k -> Type 本身标识了一个类别,那么ZippingWith f 也是如此。在xs 和ys 之间的ZippingWith f-arrow 是xs 和ys 的元素之间的f-arrows 列表,采用“zippy”方式。 HMapList f(和Container,因此)是从ZippingWith (On f (->)) 到(->) 的函子。它将函数列表提升为列表中的函数。
newtype On (f :: i -> o) (arr :: o -> o -> Type) (a :: i) (b :: i)
= On { runOn :: arr (f a) (f b) }
hmMap :: (ZippingWith (On f (->))) xs ys ->
(->) (HMapList f xs) (HMapList f ys)
hmMap ZWNil HMNil = HMNil
hmMap (ZWCons (On axy) as) (HMCons fx xs) = HMCons (axy fx) (hmMap as xs)
containerMap :: (ZippingWith (On Variable (->))) xs ys ->
(->) (Container xs) (Container ys)
containerMap as (Container name vs) = Container name (hmMap as vs)
如果f 本身是一个Functor(在这种情况下就是这样),你会得到一些从ZippingWith (->) 到ZippingWith (On f (->)) 的提升动作
zwManyMap :: Functor f => ZippingWith (->) xs ys -> ZippingWith (On f (->)) xs ys
zwManyMap ZWNil = ZWNil
zwManyMap (ZWCons axy as) = ZWCons (On (fmap axy)) (zwManyMap as)
这给了我们更多的功能:
hmMapMap :: Functor f =>
(ZippingWith (->)) xs ys ->
(->) (HMapList f xs) (HMapList f ys)
hmMapMap = hmMap . zwManyMap
containerMapMap :: (ZippingWith (->)) xs ys ->
(->) (Container xs) (Container ys)
containerMapMap = containerMap . zwManyMap
但是等等;还有更多:函子类别是一个类别,其中对象是函子(f,g),箭头是自然变换(f ~> g = forall a. f a -> g a)。 HMapList 实际上是一个双函子。您已经看到了 ZippingWith (On f (->)) 到 (->) 函子。现在查看 (~>) 到 (->) 函子。
hmLMap :: (forall x. f x -> g x) ->
HMapList f xs -> HMapList g xs
hmLMap _ HMNil = HMNil
hmLMap f (HMCons fx xs) = HMCons (f fx) (hmLMap f xs)
这个不能泛化到Container,除非你重新定义它:
data Container f xs = Container {
containerName :: String
, containerValues :: HMapList f xs
}
如果您确实选择保留您的 BarContainer 表示,containerMap 和 containerMapMap 会降级为一些可用的残余。再说一次,它们更注重镜头而不是敷衍,但它们是可行的。
-- "type-changing": e.g. BarInt can become BarChar, if desired
containerMapChanging :: ([Int] -> Bar) -> ([Double] -> Bar) ->
([String] -> Bar) -> ([Char] -> Bar) ->
BarContainer -> BarContainer
containerMapChanging i d s c (BarContainer name bs) = BarContainer name (f <$> bs)
where f (BarInt x) = i x
f (BarDouble x) = d x
f (BarString x) = s x
f (BarChar x) = c x
containerMap :: ([Int] -> [Int]) -> ([Double] -> [Double]) ->
([String] -> [String]) -> ([Char] -> [Char]) ->
BarContainer -> BarContainer
containerMap i d s c bc = containerMapChanging (BarInt . i) (BarDouble . d)
(BarString . s) (BarChar . c)
bc
containerMapMap :: (Int -> Int) -> (Double -> Double) ->
(String -> String) -> (Char -> Char) ->
BarContainer -> BarContainer
containerMapMap i d s c bc = containerMap (map i) (map d) (map s) (map c) bc
所以,例如如果我想将2 添加到BarContainer 中的每个Int 并删除每个String 的第一个字符,我可以使用containerMapMap (+2) id tail id。