是否可以在标准 ML 中创建 "generic" 函数?
Is it possible to create a "generic" function in Standard ML?
我想创建一个函数 remove_duplicates
接受任何类型的 list
(例如可以是 int list
或 bool list
或 int list list
或 whatever 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?
没有
要确定一个元素是否与另一个元素重复,它们的值必须具有可比性。 “任何类型”,或标准 ML 中的 'a
,对于相等性是不可比较的。因此,虽然您不能使用 val nub : 'a list -> 'a list
来删除重复项,但这里有四个替代选项:
@qouify 建议,built-in 相等类型 ''a
,因此您可以在 =
上使用任何东西:
val nub : ''a list -> ''a list
@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²)。 (-_- )ノ⌒┻━┻
因为是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)步骤。
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
函数:int 有 Int.compare
和 real 有Real.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
.
我想创建一个函数 remove_duplicates
接受任何类型的 list
(例如可以是 int list
或 bool list
或 int list list
或 whatever 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?
没有
要确定一个元素是否与另一个元素重复,它们的值必须具有可比性。 “任何类型”,或标准 ML 中的 'a
,对于相等性是不可比较的。因此,虽然您不能使用 val nub : 'a list -> 'a list
来删除重复项,但这里有四个替代选项:
@qouify 建议,built-in 相等类型
''a
,因此您可以在=
上使用任何东西:val nub : ''a list -> ''a list
@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²)。 (-_- )ノ⌒┻━┻因为是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)步骤。
SML 的获胜功能之一是其可组合模块系统。您可以创建一个将另一个模块作为参数(functor)的模块,而不是使用参数多态性并为函数
nubOrd
提供顺序比较函数。首先,让我们为表示类型排序的模块定义一个签名:
signature ORD = sig type t val compare : t * t -> order end
(注意
t
前面没有'
。)这意味着任何人都可以通过为
t
指定t
和相应的compare
函数来创建struct ... end : ORD
。许多 built-in 类型有 pre-definedcompare
函数:int 有Int.compare
和 real 有Real.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
.