我怎样才能最好地代表某个固定维度的笛卡儿积?

时间:2018-02-12 09:57:08

标签: haskell data-structures cartesian-product

根据@leftaroundabout的建议,这个问题被严重改写。可以在编辑历史记录中看到早期版本。

Haskell以其促进思想,允许更直接编码数学抽象这一事实而闻名。笛卡尔积是一个非常基本的心理对象,从很小的时候开始就很熟悉。然而,在Haskell中几乎没有类型。我想我需要一个,让我的想法流动,如果没有别的。 (虽然这篇文章实际上是受到我手头的一些实际代码的启发。)让我们对这个笛卡尔事物的形成有一个共同的理解(我将其称之为笛卡儿简而言之就是。

给定一系列长度d :: Int的集合(例如[[1,2], ['a', 'b']]),我希望将其元素的所有组合都放在短距离内。这意味着对它们进行操作,就好像它们处于通常的Functor,Foldable,Traversable,Monoid等等。实际上,我们可以将任何笛卡尔代表为适当嵌套的元组列表:

type Lattice = [[(x, y)]]

type WeightedLattice = [[(x, y, w)]]

zipWith2 :: (a -> b -> c) -> [[a]] -> [[b]] -> [[c]]
zipWith2 f = zipWith (zipWith f)

fmap2 :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b)
fmap2 = fmap . fmap

sequence2 :: (Traversable s, Traversable t, Monad m) => t (s (m a)) -> m (t (s a))
sequence2 = sequence . fmap sequence

类似的结构可以手工编写任何深度的嵌套。

我们现在介绍 a 笛卡尔和初始笛卡尔的区别:

  • n维笛卡尔是通过从每个集合中取一个元素并按顺序将它们与适当类型的函数组合而从异构的集合序列构造的。这是一个签名[Int,Char,Bool]的笛卡尔可以通过如下函数形成:

    f :: Int -> Char -> Bool -> Either Char Int
    f i c b = if b then Left c else Right i 
    
  • 最初的笛卡儿是由匹配arity的元组构造函数形成的:

    initial :: Int -> Char -> Bool -> (Int, Char, Bool)
    initial = (,,)
    

很容易看出,我们可以将初始笛卡尔坐标(表示为嵌套列表)转换为类似嵌套深度的任何其他笛卡尔坐标,其函数类似于:

    (fmap . ... . fmap) (uncurryN f)

然而,我们可能并不总是回来;实际上,很难从Char恢复正确的Right 3。因此,最初的笛卡尔可以用来代替任何特定的笛卡儿,但并不总是相反。

作为示例,我们可以使用上面定义的Lattice类型来可视化字段,计算其在空间中某些常规分布点的值。我们会使用一个分配值的函数来完成它。可以存在任何数量的这样的函数,描述相同点中的不同字段,每个对应一个相同尺寸的格子。但是只有一个初始莱迪思只包含坐标。

但是,我们的嵌套列表编码有其缺点。除了引出拼写出每个下一个维度的所有必要功能之外,它还不安全:没有任何东西可以帮助你避免错误地将128 x 64矩阵与64 x 128矩阵拼凑在一起并将它们压缩在一起,最终得到64 x 64个而不是;元组中的事物的顺序可能对应也可能不对应于列表嵌套的顺序。另一方面,类型系统很难对付你,不允许像foldr (.) id [replicate d concat]那样可以节省一些痛苦的东西。根本不是哈斯凯利。

但是这个系统令人失望的最深层的原因是,它并没有以任何明显的方式支持笛卡尔的非常基本的直觉:它的Monoid实例。它是一个属性,它允许我们将一个点视为没有一个,而不是一些,但任何数量p的属性,容易添加,组合或丢弃它们 - 就像列表的元素一样,确实。被钉在一定深度的嵌套和某个元组arity是你的翅膀削减。笛卡尔积是类别中的Monoid是类别理论的基本事实,但是我们可以在任意类型元组的arbirarily嵌套列表中定义Monoid吗?

因此,编写笛卡尔完成权的挑战涉及以下目标:

  • 任何尺寸。列表,矩阵和任何其他有限维空间应具有相似的界面。通常Data.List函数的一些选择应该是可实现的。

  • 类型安全。也就是说,具有在类型系统中编码的给定笛卡尔的类型和维度。例如,如果我形成一个像[1..3] x ['a', 'b']这样的空间,而另一个像[1,2] x ['a'..'c']那样,它们应该具有不同的可读类型,而不是拉链在一起。

  • 由于笛卡尔坐标由尺寸的选择决定,任何两个笛卡尔坐标都可以组合为它们的尺寸列表。例如:

    Cartesian [[1..3], ['a', 'b']] <> Cartesian [[True, False]]
    

    - 应该与:

    相同
    Cartesian [[1..3], ['a', 'b'], [True, False]]
    

    - 就像他们的生成列表一样。

  • 应该有一些初始笛卡尔的概念和放在它上面的装饰,这样除非强制丢失,否则点的坐标永远不会丢失。例如,Lattice的点的坐标应与其描述的字段的派生属性分开存储。然后,如果格子描述它们并且#34;匹配&#34;那么我们可以获得字段的叠加。

  • 最初的笛卡儿应该是一个Monoid。

我描绘了一种至少在某种程度上可以使用的可怜的东西,我会稍后将其作为答案发布,但对于上述大部分内容,我都会感到茫然。它必须采取某种类型的技巧。我很欣赏有关如何制作它的任何想法。

2 个答案:

答案 0 :(得分:4)

这个问题相当含糊,但看起来你可能对vinyl式的记录感兴趣。我的定义与vinyl正确的略有不同;用你喜欢的东西。

{-# language DataKinds, PolyKinds, TypeOperators, GADTs #-}
module Cart where
import Data.Kind (Type)
import Data.Functor.Identity

infixr 4 :<
data Rec :: [k] -> (k -> Type) -> Type where
  Nil :: Rec '[] f
  (:<) :: f a -> Rec as f -> Rec (a ': as) f

newtype HList xs = HList (Rec xs Identity)

prod :: Rec as [] -> [HList as]
prod = map HList . go
  where
    go :: Rec as [] -> [Rec as Identity]
    go Nil = [Nil]
    go (xs :< xss) = [ Identity x :< r | x <- xs, r <- go xss]

使用适当的Show个实例(直截了当但有点烦人),你会得到像

这样的东西
> prod $ [3,4,5] :< ["hello", "goodbye"] :< ['x'] :< Nil

[ H[3,"hello",'x'], H[3,"goodbye",'x'], H[4,"hello",'x']
, H[4,"goodbye",'x'], H[5,"hello",'x'], H[5,"goodbye",'x'] ]

此版本的prod可能过于具体,因为它只适用于Identity和列表。这是一个直截了当的概括,其中遍历函数&#34;分裂&#34; Rec的基础仿函数,我们使用任意Applicative而非[]

class Trav (t :: (k -> Type) -> Type) where
  trav :: Applicative g => (forall a. f a -> g (h a)) -> t f -> g (t h)

instance Trav (Rec as) where
  trav f Nil = pure Nil
  trav f (xs :< xss) = (:<) <$> f xs <*> trav f xss

这与Data.Vinyl.rtraverse类似,因为我终于弄清楚了识别。

此类记录不会形成Monoid,因为mappend无法输入。但你肯定可以追加它们:

type family (++) xs ys where
  '[] ++ ys = ys
  (x ': xs) ++ ys = x ': xs ++ ys

(><) :: Rec xs f -> Rec ys f -> Rec (xs ++ ys) f
Nil >< ys = ys
(x :< xs) >< ys = x :< (xs >< ys)

这表现得很好。特别是,您可以追加记录然后遍历它们,或者遍历它们然后附加结果。

trav f (xs >< ys) = (><) <$> trav f xs <*> trav f ys

您也可以以原则方式重新排列它们(类似于您的气泡设备)。类型

forall f k (as :: [k]) (bs :: [k]). Rec as f -> Rec bs f

可以赋予任何重新排列Rec但不关心其中的内容的函数。

因为你提到了映射:

class Functor1 (t :: (k -> Type) -> Type) where
  map1 :: (forall x. f x -> g x) -> t f -> t g

instance Functor1 (Rec as) where
  map1 f = runIdentity . trav (\x -> Identity (f x))

Zipping也有效:

rzip :: (forall x. f x -> g x -> h x)
     -> Rec as f -> Rec as g -> Rec as h
rzip f Nil Nil = Nil
rzip f (x :< xs) (y :< ys) = f x y :< rzip f xs ys

答案 1 :(得分:0)

有些疼痛可能被这种类型带走:

uni

我现在要查看有关它的方框。

  • Functor,Foldable,Traversable:开箱即用的神奇衍生物。

  • 创建:您可以通过cons逐步增长-- | Consruct a uni-dimensional Cartesian. uni :: [v] -> Cartesian v uni vs = Cartesian { _dimensions = [length vs], _values = arr vs } -- | Dimension increment. cons :: (u -> v -> w) -> [u] -> Cartesian v -> Cartesian w cons f xs Cartesian{..} = Cartesian { _dimensions = length xs: _dimensions , _values = arr [ x `f` y | x <- xs, y <- unarr _values ] } 来创建任何您想要的笛卡尔。

    uncons
  • 毁灭:你可以用-- | Dimension decrement. uncons :: (u -> (v, w)) -> Cartesian u -> Maybe ([v], Cartesian w) uncons _ Cartesian { _dimensions = [] } = Nothing uncons f Cartesian { _dimensions = (_: ds), _values = xs } = let ys = fmap (fst . f) . congr0 (product ds) $ xs zs = fmap (snd . f) . take (product ds) . unarr $ xs in Just (ys, Cartesian { _dimensions = ds, _values = arr zs }) 剥离笛卡尔层。

    (↑)
  • 换位:您可以使用(↑) x 1更改维度的顺序 bubble 设备。我没有证据,但我确定你可以获得任何你想要的换位。特别是,Data.List.traverse相当于-- | Bubble: apply a cycle from 0 to (i - 1) to the dimensions. That is, make the i-th dimension -- the first. I believe bubbles to be the generators of the symmetric group. (↑) :: Cartesian u -> Int -> Cartesian u Cartesian{..} ↑ i = let d = product . drop i $ _dimensions ds = take i _dimensions ++ drop (succ i) _dimensions -- Delete the i-th. in Cartesian { _dimensions = ds , _values = arr . concat $ ($ _values) <$> (congr d <$> [0..pred d]) }

    cons
  • uncons(↑)appendWith :: (u -> v -> w) -> Cartesian u -> Cartesian v -> Cartesian w appendWith f x y = Cartesian { _dimensions = _dimensions x ++ _dimensions y , _values = arr [ x `f` y | x <- unarr (_values x), y <- unarr (_values y) ] } 允许您再次切片和组合任意两个笛卡儿或其中的部分。但也有一种方法可以直接结合笛卡尔人:

    glue f x y | _dimensions x == _dimensions y
                    = Cartesian
                        { _dimensions = _dimensions x
                        , _values = arr $ zipWith f (unarr $ _values x) (unarr $ _values y)
                        }
               | otherwise = undefined
    
  • 你也可以拉链:

    mempty

在此方面,您必须明确提供一个二元函数,它可以将您在笛卡尔中的类型组合在一起。这通常是元组构造函数,但您不限于此。

我无法定义Monoid的实例,特别是{{1}}部分,初始事情甚至不在这附近。