是否可以在标准 ML 中创建 "generic" 函数?

Is it possible to create a "generic" function in Standard ML?

我想创建一个函数 remove_duplicates 接受任何类型的 list(例如可以是 int listbool listint list listwhatever list) 和 returns 没有重复的相同列表,这在标准 ML 中可能吗?

是的。通过使用参数多态性,这在 SML 中是可能的。您需要一个最通用类型 'a list -> 'a list 的函数,其中 'a 是一个类型变量(即范围超过类型的变量),它将被读取为 alpha。

关于如何应用它的一些更具体的例子(fun 之后的显式类型变量是可选的):

fun 'a id (x : 'a) : 'a = x

这里我们有类型为 'a -> 'a 的恒等函数。

我们可以声明具有某种程度的类型特殊化的类似函数,例如

fun map _ [] = []
  | map f (x::xs) = f x :: map f xs

其中 map 具有最通用的类​​型 ('a -> 'b) -> 'a list -> 'b list,即采用两个柯里化参数,一个具有某种函数类型,另一个具有某种列表类型(与函数的域一致)和 returns 一个新列表,其类型由函数的密码域给出。

对于您的特定问题,您可能还需要采用相等函数来确定什么是“重复”,或者您可能会限制自己使用“相等类型”(可以与 op=,由带有两个前导撇号的类型变量表示,例如,''a).

是的,sml 提供了多态性来做这样的事情。在许多情况下,您实际上并不关心列表(或其他结构)中项目的类型。例如,此函数检查(已存在于 List 结构中)列表中是否存在某个项目:

fun exists _ [] = false
  | exists x (y :: l) = x = y orelse exists x l

该函数适用于任何类型的列表,只要为该类型定义了等于运算符(该类型称为相等类型)。您可以对 remove_duplicates 执行相同的操作。为了使用不相等类型的项目列表,你必须给 remove_duplicates 一个额外的函数来检查两个项目是否相等。

Is a function that takes a list of any type and returns the list without duplicates possible in Standard ML?

没有

要确定一个元素是否与另一个元素重复,它们的值必须具有可比性。 “任何类型”,或标准 M​​L 中的 'a,对于相等性是不可比较的。因此,虽然您不能使用 val nub : 'a list -> 'a list 来删除重复项,但这里有四个替代选项:

  1. @qouify 建议,built-in 相等类型 ''a,因此您可以在 = 上使用任何东西:

    val nub : ''a list -> ''a list
    
  2. @kopecs 建议的,一个以相等运算符作为参数的函数:

    val nub : ('a * 'a -> bool) -> 'a list -> 'a list
    

    这是 1. 的概括,因为这里 nub op= : ''a list -> ''a list。这个解决方案有点简洁,因为它不仅可以让您删除重复项,还可以删除任意等价 classes 的冗余代表,例如nub (fn (x, y) => (x mod 3) = (y mod 3)) 只会保留模 3 不同的整数。但它的复杂度是 O(n²)。 (-_- )ノ⌒┻━┻

  3. 因为是O(n²),nub is considered harmful.

    正如文章还建议的那样,替代方法是使用排序而不是相等来将复杂性降低到 O(n log n)。在 Haskell 中,这意味着仅更改类型 class 约束:

    nub    :: Eq a  => [a] -> [a]
    nubOrd :: Ord a => [a] -> [a]
    

    并调整算法,在 SML 中表达此约束会变得稍微复杂一些。虽然我们 ''a 来表示 Eq a => a(我们可以在输入中使用 =),但我们 没有 对可以与 less/equal/greater 比较的元素有类似的特殊语法支持,我们也没有类型 classes。我们有以下built-in订单类型:

    datatype order = LESS | EQUAL | GREATER
    

    因此,如果您喜欢 kopecs 的解决方案,运行ning 时间更好的变体是:

    val nubOrd : ('a * 'a -> order) -> 'a list -> 'a list
    

    因为它可以使用类似以前见过的元素的数学集,使用某种平衡搜索树实现; n 插入每个复杂度 O(log n) 总共需要 O(n log n)步骤。

  4. SML 的获胜功能之一是其可组合模块系统。您可以创建一个将另一个模块作为参数(functor)的模块,而不是使用参数多态性并为函数 nubOrd 提供顺序比较函数。

    首先,让我们为表示类型排序的模块定义一个签名

    signature ORD =
    sig
      type t
      val compare : t * t -> order
    end
    

    (注意t前面没有'。)

    这意味着任何人都可以通过为 t 指定 t 和相应的 compare 函数来创建 struct ... end : ORD。许多 built-in 类型有 pre-defined compare 函数:intInt.comparerealReal.compare.

    然后,定义一个tree-based集合数据结构;我使用了二叉搜索树,并且跳过了大多数函数,但跳过了执行此壮举所必需的函数。理想情况下,您可以扩展接口并使用更好的树类型,例如 self-balancing 树。 (不幸的是,由于您已将此问答标记为 SML/NJ 和 Moscow ML,我不确定要使用哪个模块,因为它们在平衡方面以不同的方式扩展了标准库树。)

    functor TreeSet (X : ORD) =
    struct
      type t = X.t
      datatype 'a tree = Leaf | Branch of 'a tree * 'a * 'a tree
    
      val empty = Leaf
    
      fun member (x, Leaf) = false
        | member (x, Branch (left, y, right)) =
            case X.compare (x, y) of
                 EQUAL => true
               | LESS => member (x, left)
               | GREATER => member (x, right)
    
      fun insert (x, Leaf) = Branch (Leaf, x, Leaf)
        | insert (x, Branch (left, y, right)) =
            case X.compare (x, y) of
                 EQUAL => Branch (left, y, right)
               | LESS  => Branch (insert (x, left), y, right)
               | GREATER => Branch (left, y, insert (x, right))
    end
    

    最后,ListUtils 仿函数包含 nubOrd 效用函数。仿函数采用 X : ORD 结构,就像 TreeSet 仿函数所做的那样。它通过使用相同的排序模块专门化 TreeSet 仿函数来创建 XSet 结构。然后它使用这个 XSet 有效地 记录它之前看到的元素。

    functor ListUtils (X : ORD) =
    struct
      structure XSet = TreeSet(X)
    
      fun nubOrd (xs : X.t list) =
        let
          val init = ([], XSet.empty)
          fun go (x, (ys, seen)) =
            if XSet.member (x, seen)
              then (ys, seen)
              else (x::ys, XSet.insert (x, seen))
        in rev (#1 (foldl go init xs))
        end
    end
    

    使用此仿函数删除 int list 中的重复项:

    structure IntListUtils = ListUtils(struct
                                         type t = int
                                         val compare = Int.compare
                                       end)
    
    val example = IntListUtils.nubOrd [1,1,2,1,3,1,2,1,3,3,2,1,4,3,2,1,5,4,3,2,1]
                                   (* [1,  2,  3,              4,      5] *)
    

    所有这些混乱的目的是 nubOrd 没有直接的额外函数参数。

    不幸的是,为了将其扩展到 int list list,您需要为该类型创建 compare 函数,因为与 Int.compare 不同,没有通用函数在标准库中也可用。 (这是 Haskell 更符合人体工学的地方。)

    所以你可能会去写一个通用的字典列表比较函数:如果你知道如何比较类型 'a 的两个元素,你就知道如何比较这些列表的两个列表,无论元素是什么类型是:

    fun listCompare _ ([], []) = EQUAL   (* empty lists are equal *)
      | listCompare _ ([], ys) = LESS    (* empty is always smaller than non-empty *)
      | listCompare _ (xs, []) = GREATER (* empty is always smaller than non-empty *)
      | listCompare compare (x::xs, y::ys) =
          case compare (x, y) of
               EQUAL => listCompare compare (xs, ys)
             | LESS => LESS
             | GREATER => GREATER
    

    而现在,

    structure IntListListUtils = ListUtils(struct
                                             type t = int list
                                             val compare = listCompare Int.compare
                                           end)
    val example2 = IntListListUtils.nubOrd [[1,2,3],[1,2,3,2],[1,2,3]]
                                        (* [[1,2,3],[1,2,3,2]] *)
    

    因此,尽管 [1,2,3][1,2,3,2] 包含重复项,但当您 compare 它们时,它们不是 EQUAL。但是第三个元素 EQUAL 到第一个元素,因此它作为重复项被删除。

一些最后的观察:

  • 你可能会认为,即使每个 compare 只有 运行 O(log n) 次,单个 compare对于一些复杂的数据结构,比如一个(whatever * int) list list可能还是很昂贵的。因此,您可以在此处进行的另一项改进是缓存每个 compare 输出的结果,这实际上是 Haskell 的 nubOrdOn 运算符所做的。 ┳━┳ヽ(ಠل͜ಠ)ノ

  • 函子方法在 Jane Street's OCaml Base library 中被广泛使用。快速的解决方案是传递一个 'a * 'a -> order 函数每次你 nub 都会发现一些东西。不过,一个道理是,虽然模块系统确实增加了冗长,但如果您在标准库中提供足够多的这种机制,它将变得非常方便。

  • 如果您认为从 O(n²)O(n log n) 的改进还不够, 考虑 Fritz Henglein 的 Generic top-down discrimination for sorting and partitioning in linear time (2012) and Edward Kmett's Haskell discrimination package's nub 对于 O(n) nub.