问题描述
我正在学习SML,并尝试创建一个名为mySet的数据类型,该数据类型可以是任何整数或实数列表,但没有重复项且按顺序排列。到目前为止,我已经制作了数据类型和一些执行我需要的功能的函数,然后将其返回到该数据类型中可以正常工作的列表。但是我意识到,也可以使用数据类型的构造函数来完全绕过需求。对于我所需要的,我可以只使用该函数,但是我真的很想知道是否有什么办法可以解决该问题?如果列表不符合要求,那么我针对该数据类型的大多数功能将无法正常工作。
datatype 'a set = Set of 'a list | Empty;
(* takes (item,list) and removes any copies of item from list *)
fun cleanList(a,[]) = []
|cleanList(a,b::rest) =
if b = a then cleanList(a,rest)
else
b::cleanList(a,rest);
(*uses cleanList to make a list with all the items,no copies*)
fun removeDup([]) = []
| removeDup(a::rest) =
let
val cleanRest = cleanList(a,rest);
in
a::removeDup(cleanRest)
end;
(*uses above 2 functions,then puts the list in order *)
fun makeSet([]) = Empty
|makeSet(inputList) =
let
val cleanList = removeDup(inputList)
val sortedList = ListMergeSort.sort (fn(x,y) => x > y) cleanList;
in
Set(sortedList)
end;
val testList = [27,81,27,3,4,5,7];
makeSet(testList); (* returns Set [3,7,81] *)
Set([1,1,1]); (*Set [1,1] which I don't want to allow *)
解决方法
我意识到,也可以使用数据类型的构造函数来完全绕过要求。对于我所需要的,我可以只使用该函数,但是我真的很想知道是否有什么办法可以解决该问题?
有!您的基本构造函数将破坏数据类型的不变式,因此您想隐藏它,而只公开smart constructor,它在某些输入上故意失败,并且不允许无效状态。
正如molbdnilo所说,之所以称为abstract type,是因为您隐藏了实现方式并通过其智能构造函数接口公开了它,该接口具有您想要的任何行为。您也可以将其称为opaque type。
实现此目标的每种方法的共同点是,您有一个局部范围,在该范围内声明了数据类型,但是仅保留了智能构造函数的外部接口。为了探索所需的语言功能,我尝试简单地写:
val (fmap,pure) =
let
datatype 'a maybe = Just of 'a | Nothing
fun fmap f Nothing = Nothing
| fmap f (Just x) = Just (f x)
fun pure x = Just x
in (fmap,pure)
end
但是我的SML编译器实际上拒绝了该程序:
! in (fmap,pure)
! ^^^^
! Type clash: expression of type
! ('a -> 'b) -> 'a maybe -> 'b maybe
! cannot have type
! 'c
! because of a scope violation:
! the type constructor maybe is a parameter
! that is declared within the scope of 'c
因此,我们需要了解专门为此目的设计的SML语言功能之一:
更新:@ruakh指出我忘记了local
。
以下是使用local
-in
-end
的示例:
local
datatype 'a maybe = Just of 'a | Nothing
in
fun fmap f Nothing = Nothing
| fmap f (Just x) = Just (f x)
fun pure x = Just x
end
两个函数fmap
和pure
之间共享数据类型,但是定义从外部接口隐藏。您可以具有多个本地定义,包括辅助函数。
并且构造函数是隐藏的:
> New type names: =maybe
val ('a,'b) fmap = fn : ('a -> 'b) -> 'a maybe -> 'b maybe
val 'a pure = fn : 'a -> 'a maybe
let
和local
在Difference between "local" and "let" in SML
以下是使用abstype
的同一示例:
abstype 'a maybe = Just of 'a | Nothing
with
fun fmap f Nothing = Nothing
| fmap f (Just x) = Just (f x)
fun pure x = Just x
end
您会看到Just
和Nothing
的隐藏方式:
> New type names: maybe
type 'a maybe = 'a maybe
val ('a,'b) fmap = fn : ('a -> 'b) -> 'a maybe -> 'b maybe
val 'a pure = fn : 'a -> 'a maybe
但是您也可以使用不透明的模块。 This StackOverflow answer精确地介绍了集合函子的骨架如何工作。 ML for the Working Programmer的第7章介绍了如何定义以模块作为参数的模块(仿函数)。这是参数多态性的替代方法。