【发布时间】:2018-07-22 10:57:39
【问题描述】:
根据@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 -
初始笛卡尔是用匹配元组的元组构造函数形成的:
initial :: Int -> Char -> Bool -> (Int, Char, Bool) initial = (,,)
很容易看出,我们可以将表示为嵌套列表的初始笛卡尔坐标转换为具有类似嵌套深度的任何其他笛卡尔坐标,其函数类似于:
(fmap . ... . fmap) (uncurryN f)
但是,我们可能不会总是回来;实际上,很难从Right 3 中恢复正确的Char。因此,初始笛卡尔坐标可以用来代替任何特定笛卡尔坐标,但并非总是相反。
作为一个例子,我们可以使用上面定义的Lattice 类型来可视化一个字段,为空间中一些规则分布的点计算它的值。我们将使用一个分配坐标值的函数来做到这一点。可能有任意数量的这样的函数,在相同的点上描述不同的字段,每个函数对应一个相似维度的格子。但是只有一个初始格子,它只包含坐标。
但是,我们的嵌套列表编码有其缺点。除了令人厌烦地为每个下一个维度拼出所有必要的函数之外,它是不安全的:没有什么可以使您免于将 128 x 64 矩阵与 64 x 128 矩阵混淆并将它们压缩在一起,最终得到 64 x 64 一个代替;元组中事物的顺序可能对应于列表嵌套的顺序,也可能不对应。另一方面,类型系统对你很不利,不允许像foldr (.) id [replicate d concat] 这样可以节省一些痛苦的东西。一点都没有。
但对这个系统最失望的根源在于,它不以任何明显的方式支持笛卡尔最基本的直觉:它的 Monoid 实例。它是一个属性,使我们可以将一个点视为不是一个,不是一些,而是任意数量的p 属性,可以轻松添加、组合或丢弃它们——实际上就像列表的元素一样。被钉在一定深度的嵌套和一定的元组数量上,你的翅膀就会被剪掉。笛卡尔积是 Set 范畴中的 Monoid 是范畴论的一个基本事实,但是我们可以在任意类型元组的任意嵌套列表上定义 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的点坐标应与其描述的字段的派生属性分开存储。比如说,如果描述它们的格“匹配”,我们就可以获得场的叠加。初始笛卡尔应该是 Monoid。
我画了一些至少有点可用的可怜的东西,我会马上把它作为答案发布,但对于上述大多数点,我都不知所措。它必须采取一些类型的诡计。我很欣赏有关如何制作它的任何想法。
【问题讨论】:
-
这似乎是一个有趣的问题,但我发现很难准确理解它的含义。您能否添加更多代码/伪代码/伪签名?特别是,与元组的关系是什么——你可以得到最接近的元组,为什么这对你来说不满意?
-
这听起来很像一篇(我认为相当有名的)论文,我不记得它的标题,关于使用类型类和类型组合器构造一个可扩展的表达式类型。基本上,它是一个开放副产品的模型。你可以做同样的事情,但是构建一个开放的产品。我希望这里的其他人可以帮助我了解论文的名称
-
啊,在这里,Data Types A La Carte。我认为您可以使用类似的技术来解决您的问题。
-
也许是个愚蠢的建议,但如果您只想从一些列表到元组列表,并且每个列表都有固定数量,您可以使用应用程序:
f = liftA2 (,)然后f [1,2] [T, F]是@ 987654346@,您也可以使用liftA3 (,,)。显然,如果您想使用任意d执行此操作,这将不起作用。
标签: haskell data-structures cartesian-product