Haskell 商类型如何帮助安全地公开模块内部?

Haskell 商类型如何帮助安全地公开模块内部?,haskell,functional-programming,type-systems,type-theory,Haskell,Functional Programming,Type Systems,Type Theory,在阅读商类型及其在函数式编程中的使用时,我偶然发现。作者举了一个模块的例子,该模块提供了大量需要访问模块内部的功能: Data.Set有36个功能,而确保集合含义(“这些元素是不同的”)真正需要的是toList和fromList 作者的观点似乎是,如果我们忘记了一些仅使用模块内部就可以有效实现的功能,那么我们需要“打开模块并打破抽象” 他接着说 我们可以用商类型来缓解所有这些混乱 但没有解释这一说法 所以我的问题是:商类型在这里有什么帮助 编辑 我做了更多的研究,发现了一篇论文。它详细阐述了声

在阅读商类型及其在函数式编程中的使用时,我偶然发现。作者举了一个模块的例子,该模块提供了大量需要访问模块内部的功能:

Data.Set
有36个功能,而确保集合含义(“这些元素是不同的”)真正需要的是
toList
fromList

作者的观点似乎是,如果我们忘记了一些仅使用模块内部就可以有效实现的功能,那么我们需要“打开模块并打破抽象”

他接着说

我们可以用商类型来缓解所有这些混乱

但没有解释这一说法

所以我的问题是:商类型在这里有什么帮助


编辑

我做了更多的研究,发现了一篇论文。它详细阐述了声明商容器,并在摘要和引言中提到“有效”一词。但如果我没有误读,它不会给出任何有效表示“隐藏在”商容器后面的示例


编辑2


在第3章中会有更多的介绍。事实上,商类型可以实现为相依和。介绍了抽象类型的视图(在我看来,它与类型类非常相似),并提供了一些相关的Agda代码。然而,本章的重点是关于抽象类型的推理,因此我不确定这与我的问题有何关系。

免责声明:我只是在阅读了这个问题后阅读了商类型

我想作者只是说集合可以被描述为列表上的商类型。Ie:(编一些类似haskell的语法):

也就是说,一个
集合a
就是一个
[a]
,两个
集合a
之间的相等取决于
是否排序。基础列表的nub
是相等的

我想我们可以这样做:

import Data.List

data Set a = Set [a] deriving (Show)

instance (Ord a, Eq a) => Eq (Set a) where
  (Set xs) == (Set ys) = (sort $ nub xs) == (sort $ nub ys)

不确定这是否是作者的意图,因为这不是实现集合的特别有效的方法。有人可以随意纠正我。

我将给出一个简单的例子,其中比较清楚。诚然,我自己并不认为这会有效地转化为类似于
Set

data Nat = Nat (Integer / abs)
为了安全地使用它,我们必须确保任何函数
Nat->T
(为了简单起见,带有一些非商T)都不依赖于实际的整数值,而只依赖于其绝对值。要做到这一点,实际上没有必要完全隐藏
Integer
;这足以阻止您直接匹配它。相反,编译器可能会重写匹配项,例如

even' :: Nat -> Bool
even' (Nat 0) = True
even' (Nat 1) = False
even' (Nat n) = even' . Nat $ n - 2
可以改写为

even' (Nat n') = case abs n' of
           [|abs 0|]  -> True
           [|abs 1|]  -> False
           n          -> even' . Nat $ n - 2
这样的重写会指出等价性冲突,例如

bad (Nat 1) = "foo"
bad (Nat (-1)) = "bar"
bad _ = undefined
将重写为

bad (Nat n') = case n' of
      1 -> "foo"
      1 -> "bar"
      _ -> undefined
这显然是一个重叠的模式。

我最近做了一个评论,我在这里被一条评论引导。除了问题中引用的论文外,博客文章还可能提供一些额外的上下文

答案其实很简单。一种方法是问这样一个问题:为什么我们首先要为
data.Set
使用抽象数据类型

有两个截然不同的原因。第一个原因是将内部类型隐藏在接口后面,以便将来可以替换一个全新的类型。第二个原因是对内部类型的值强制执行隐式不变量。商类型及其双重子集类型允许我们使不变量显式,并由类型检查器强制执行,这样我们就不再需要隐藏表示。所以让我非常清楚:商(和子集)类型不会为您提供任何实现隐藏。如果实现
Data.Set
时使用列表表示商类型,然后决定使用树,则需要更改使用类型的所有代码

让我们从一个简单的例子开始(leftaroundabout's)。Haskell有一个
Integer
类型,但不是
Natural
类型。使用组合语法将
Natural
指定为子集类型的简单方法是:

type Natural={n::Integer | n>=0}
我们可以使用一个智能构造函数将其实现为一个抽象类型,当给定一个负的
整数时,该构造函数会抛出一个错误。此类型表示只有
Integer
类型的值的子集有效。实现此类型的另一种方法是使用商类型:

type Natural=Integer/~其中n~m=abs n==abs m
对于某些类型
T
的任何函数
h::X->T
都会在
X
上产生一个商类型,商类型由等价关系
X~y=hx==hy
确定。这种形式的商类型更容易编码为抽象数据类型。但是,一般来说,可能没有这样方便的功能,例如:

类型对a=(a,a)/~其中(a,b)~(x,y)=a==x&&b==y | | a==y&&b==x
(至于商类型如何与setoid相关,商类型是一个setoid,它强制你尊重它的等价关系。)自然的第二个定义有两个值表示
2
,比如说。即,
2
-2
。商类型方面说,我们可以对底层的
整数
做任何我们想做的事情,只要我们永远不会产生区分这两个代表的结果。另一种方式是,我们可以使用子集类型将商类型编码为:

X/~=对于所有a。{f::X->a | forEvery(\(X,y)->X~y=>fx==fy)}->a
不幸的是,这相当于检查函数的相等性

缩小后,子集类型会在上添加约束
bad (Nat n') = case n' of
      1 -> "foo"
      1 -> "bar"
      _ -> undefined