根据@leftaroundabout的建议,本问题已经进行了严重改写。早期版本可以在编辑历史中看到。
Haskell因为有助于思考而闻名,可以更直接地编码数学抽象。笛卡尔积是一个非常基本的心理对象,许多人从童年时代就熟悉了它。然而,在Haskell中几乎没有一个类型可以表示它。我认为我需要一个,即使只是为了让我的思维流畅。(尽管这篇文章实际上是由我手头的一些务实代码启发的。)让我们形成一个关于这个笛卡尔东西(我将简称为Cartesian)的共同理解。
给定长度为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
可以手动编写类似的结构,以适应任何嵌套深度。
我们现在介绍 一种 笛卡尔和 初始 笛卡尔之间的区别:
An n-dimensional Cartesian is constructed from a heterogeneous sequence of collections by taking one element from each collection and combining them, orderly, with a suitably typed function. This, a Cartesian of signature [Int, Char, Bool] may be formed by a function such as:
f :: Int -> Char -> Bool -> Either Char Int f i c b = if b then Left c else Right i
The initial Cartesian is formed with the tuple constructor of matching arity:
initial :: Int -> Char -> Bool -> (Int, Char, Bool) initial = (,,)
很容易看出,我们可以使用类似于以下函数的方式将初始笛卡尔表示为嵌套列表,转换为任何其他相同嵌套深度的笛卡尔:
(fmap . ... . fmap) (uncurryN f)
然而,我们并不总是能获得正确的
Char
,从一个 Right 3
中恢复它将会很困难。因此,初始笛卡尔积可以代替任何特定的笛卡尔积,但反之则不一定成立。例如,我们可以使用上述定义的
Lattice
类型来可视化一个场域,并计算其在空间中某些正则分布点的值。我们可以用函数为坐标赋值,任意数量的这样的函数可描述相同点上的不同场,每个对应于类似尺寸的格子。但只有一个初始的 Lattice 只包含坐标。然而,我们的嵌套列表编码有其缺点。除了需要拼出每个下一个维度所需的所有必要函数外,还不安全:没有什么可以防止您错误地混淆一个 128 x 64 矩阵和一个 64 x 128 矩阵并将它们压缩在一起,最终得到一个 64 x 64 的矩阵;元组中的事物顺序可能与列表嵌套的顺序相对应或者不相关。另一方面,类型系统也会起到抵制作用,不允许像
foldr (.) id [replicate d concat]
这样的事情,这可以减少一些痛苦。这样并不像 Haskell。但是,这个系统最令人失望的根源在于它没有以任何明显的方式支持笛卡尔积的非常基本的直觉:其 Monoid 实例。这个属性使我们可以将一个点视为具有不止一个、不是一些,而是任意数量的属性
p
,轻松地添加、组合或抛弃它们——就像列表元素一样。被钉在某个嵌套深度和某个元组的顺序上,就好像剪断了你的翅膀一样。笛卡尔积乘积是从类别论中的 Set 类别中的幺半群的基本事实,但我们能否定义一个在任意嵌套的元组列表上的 Monoid?因此,编写一个正确的 Cartesian 的挑战涉及以下目标:
Any dimension. A list, a matrix, and any other finite-dimensional space should have like interface. Some selection of the usual
Data.List
functions should be implementable.Type safety. That is, having the types and the dimensions of a given Cartesian encoded in the type system. For example, if I form a space like
[1..3] x ['a', 'b']
, and another like[1,2] x ['a'..'c']
, they should have distinct readable type, and not zip together.As the Cartesian is determined by the selection of the dimensions, any two Cartesians may be combined just as the lists of their dimensions. For example:
Cartesian [[1..3], ['a', 'b']] <> Cartesian [[True, False]]
-- should be the same thing as:
Cartesian [[1..3], ['a', 'b'], [True, False]]
-- Just the same as their generating lists would.
There should be some notion of the initial Cartesian and the decorations placed over it, so that the coordinates of points are never lost unless the loss is forced. For example, the coordinates of the points of a
Lattice
should be stored separately of the derived properties of the field it describes. We may then, say, obtain a superposition of fields if the Lattices describing them "match".The initial Cartesian should be a Monoid.
我勾画了一个至少有点可用的类型,并将其作为答案发布,但对于上述大部分内容,我感到困惑。这必须需要一些类型的技巧。我欣赏任何关于如何制作它的想法。
IArray
实例是否适合您?您可以选择将其索引(Ix
)设置为元组,但由于数组是线性存储的,因此对它们有简单的Traversable
等实例。 - Petrf = liftA2 (,)
然后f [1,2] [T, F]
就是[(1,T),(1,F),(2,T),(2,F)]
,你还可以做例如liftA3 (,,)
。显然,如果你想对任意的d
进行此操作,则无法使用此方法。 - Dan RobertsonSets
是一个带有笛卡尔积的幺范畴,但是你必须以某种方式处理它的连贯性(你似乎想立即“展平”一切,不区分(A x B) x C
和A x (B x C)
)。我不太清楚“初始笛卡尔积”应该是什么。Sets
有一个初始对象:空集,它不是一个幺半群,因为它是空的,因此缺少一个恒等元素。 - Andrey Tyukin