我正在尝试实现以下目标:
我有一个参数化类型,我们称之为变量。这是一个函子 然后我想要一个变量容器(任何变量,变量Int,变量双精度,变量字符串等)
我也希望这个容器也是仿函数。
我设法制作了一个参数化的容器FooContainer,但我想处理异构类型。
因此,我创建了Bar代数数据类型和BarContainer。 (如此处https://wiki.haskell.org/Heterogenous_collections#Algebraic_datatypes所示) 但是我不明白如何使BarContainer成为Functor,因为它的构造函数不带任何参数。
import Data.List
data Variable a = Variable {
varName :: String
,value :: [a] } deriving (Show,Read,Eq)
instance Functor Variable where
fmap f (Variable name vals ) = Variable name (fmap f vals)
data FooContainer a = FooContainer {
fooname:: String
, pdata :: [Variable a]
} deriving (Show,Read,Eq)
instance Functor FooContainer where
fmap f (FooContainer n p ) = FooContainer n ( Data.List.map (\x-> fmap f x) p)
data Bar = BarInt [Int] | BarDouble [Double] | BarString [String] | BarChar [Char] deriving (Show,Read,Eq)
data BarContainer = BarContainer {
nameB:: String
, pdataB :: [Bar]
} deriving (Show,Read,Eq)
fooC = FooContainer "foo Container" [Variable "v1" [5,6], Variable "v2" [2,6,8]]
fooC_plus2 = fmap (+2) fooC
barC = BarContainer "bar Container" [ BarInt [5,1], BarDouble [3.2,2,6], BarString ["bob", "dupont"]]
--barC_plus2 ?
main = print $ "Hello, world!" ++ ( show fooC_plus2) ++ (show barC)
答案 0 :(得分:1)
(这更像是评论而不是答案,但我需要更多空间。)
如上所述,似乎无法实现这种容器,但是也许您对类似的东西还可以。
问题1 :
假设我们有一个异构容器c
,其中包含Variable Int
和Variable String
的混合物。然后,考虑任何f :: Int -> Int
(例如f = succ
)。
fmap f c
是什么?我们不能将f
应用于所有变量。 f
仅适用于Int
个吗?这将需要一些运行时类型检查,即,我们需要在此添加Typeable
约束,但是Functor
不允许在fmap
上添加此类约束。
问题2 :
要使用fmap f c
,自变量c
的类型Container T
必须为T
。索引T
应该是什么?
也许根本没有索引。索引也许是异构容器内部类型的类型级别列表。例如。 Container '[Int,Int,String,Int]
。
在任何情况下,Functor
都无法使用此功能。
也许您想要的是一个自定义函数,例如
notFmap :: (Typeable a, Typeable b) => (a -> b) -> Container -> Container
或
notFmap :: (a -> b) -> Container t -> Container (Replace a b t)
其中Replace
是合适的类型族,它处理索引列表t
并将a
替换为b
。
答案 1 :(得分:1)
您想要一个容器,其中包含一个String
名称,然后包含一个Value
个不同类型的列表。使用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
库中的运算符来操作f x
的“槽”中包含的Container
,并进行类型更改。也就是说,一旦您在列表中用Elem
选择了一个位置,就可以使用{{1}将Functor
替换为a
,b
}}。不过,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)
内的所有Variable Int
(可能会更改类型,例如使用Container
) ,则可以改编one of my other answers的一部分。
show
也是适当的小写字母-“ f” 。让我定义一类类别:
Container
如果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
和ZippingWith f
之间的xs
箭头是ys
和f
的元素之间的xs
箭头的列表,在“ zippy”时尚。 ys
(因此是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
实际上是一个bifunctor。您已经看到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
中的每个Int
添加BarContainer
并去除每个String
的第一个字符,则可以使用containerMapMap (+2) id tail id
。