当使用构造函数时,SML 是否有办法 运行 参数上的函数?

Does SML have a way to run a function on the argument when a Constructor is used?

我正在学习 SML 并尝试制作一个名为 mySet 的数据类型,它可以是任何整数或实数列表,但没有重复且按顺序排列。到目前为止,我已经制作了数据类型和一些函数来完成我需要的列表,然后 return 它在该数据类型中工作正常。但我意识到也可以使用数据类型的构造函数来代替,这完全绕过了要求。对于我需要的,我可以只使用该功能,但我真的很想知道是否有任何方法可以解决这个问题?如果列表不符合要求,我的大多数数据类型函数将无法正常工作。

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, 4, 27, 81, 3, 3, 7];

makeSet(testList); (* returns Set [3,4,5,7,27,81] *)

Set([1,1,1,1,1,1]); (*Set [1,1,1,1,1,1] which I don't want to allow *)

I realized that the constructor for the datatype could also be used instead which completely bypasses the requirements. For what I need, I can just use the function, but I'd really like to know if there's any way I can patch up that problem?

有!你的基本构造函数会破坏你的数据类型的不变量,所以你想隐藏它并只公开一个 smart constructor 故意在某些输入上失败并且不允许无效状态。

正如 molbdnilo 所说,这称为 abstract type because you hide the way it is implemented and expose it through its smart constructor interface, which has whatever behavior you want it to. You can also call it an 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

数据类型在两个函数 fmappure 之间共享,但定义对外部接口是隐藏的。您可以有多个本地定义,包括辅助函数。

并且隐藏了构造函数:

> New type names: =maybe
  val ('a, 'b) fmap = fn : ('a -> 'b) -> 'a maybe -> 'b maybe
  val 'a pure = fn : 'a -> 'a maybe

letlocal

中进一步讨论

这是使用 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

你可以看到 JustNothing 是如何隐藏的:

> 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 Whosebug answer covers precisely how the skeleton for a set functor works. Chapter 7 of ML for the Working Programmer 介绍了如何定义将模块作为参数(仿函数)的模块。这是参数多态性的替代方法。