如何使同一个参数化类型的异构类型的容器?

时间:2018-10-08 19:43:38

标签: haskell

我正在尝试实现以下目标:

我有一个参数化类型,我们称之为变量。这是一个函子 然后我想要一个变量容器(任何变量,变量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)

2 个答案:

答案 0 :(得分:1)

(这更像是评论而不是答案,但我需要更多空间。)

如上所述,似乎无法实现这种容器,但是也许您对类似的东西还可以。

问题1 : 假设我们有一个异构容器c,其中包含Variable IntVariable 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替换为ab }}。不过,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 fZippingWith f之间的xs箭头是ysf的元素之间的xs箭头的列表,在“ zippy”时尚。 ys(因此是HMapList f)是从ContainerZippingWith (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

但是等等;还有更多:函子类别是对象是函子(fg)和箭头是自然变换(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表示形式,则containerMapcontainerMapMap会降级为某些可用的残余。再说一次,它们比功能更丰富,但可行。

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