OCaml - 向该列表添加一个包含元组列表的新元组

时间:2012-11-07 21:22:10

标签: list recursion closures ocaml tuples

我正在使用一些简单的命令在OCaml中编写交互式计算器。除其他外,用户应该能够定义自己的简单函数(数学函数),例如

let f(x) = x
let g(x) = 2*f(x)

现在,函数应该像函数式语言一样处理,这意味着它们应该记住它们的创建时间环境。这意味着,使用函数我必须保持其环境的闭包,即函数和变量。

我将当前定义的函数保存在形成为(functions_present_at_the_time_of_creation, variables_present_at_the_time_of_creation, function_name, function_argument_names, function_formula)的元组列表中。当我尝试将新函数添加到函数列表中时(让我们假设它当前没有定义,我不必覆盖任何东西),我反复迭代到函数列表的末尾,并且想要添加一个新的元组。

问题是,假设我当前的函数列表是(a*b*c*d*e) list类型,当我尝试将一个元组添加到它的末尾时,它将其类型更改为((a*b*c*d*e) list*f*g*h*i) list。我能做些什么才能将这样的列表添加到自身中,封装在一个元组中?

这是我在尝试找到此问题的解决方法时写的一些简单的SSCCE。

let rec add_to_end list list_copy dummy = match list with
| [] -> [(list_copy, dummy)]
| h::t -> h::(add_to_end t list_copy dummy) 

let add list dummy = add_to_end list list dummy

这个尝试使用列表的副本。下面的内容是在不使用副本的情况下编写的(当然这两个示例都不起作用):

let rec add_to_end list dummy = match list with
| [] -> [(list, dummy)]
| h::t -> h::(add_to_end t dummy) 

第一个例子在尝试使用add函数时不起作用,但是当这样做时(在解释器中):

let l = [];;
let l = add_to_end l l 1;;
let l = add_to_end l l 2;;
let l = add_to_end l l 3;;

然后它工作正常。我很感激任何帮助,我也可以考虑改变设计,非常欢迎任何建议。

编辑:这是上述命令的输出:

# let l = [];;
val l : 'a list = []
# let l = add_to_end l l 1;;
val l : ('a list * int) list = [([], 1)]
# let l = add_to_end l l 2;;
val l : (('a list * int) list * int) list = [([], 1); ([([], 1)], 2)]
# let l = add_to_end l l 3;;
val l : ((('a list * int) list * int) list * int) list =
[([], 1); ([([], 1)], 2); ([([], 1); ([([], 1)], 2)], 3)]

1 个答案:

答案 0 :(得分:3)

很难说你是否意识到OCaml列表是不可变的。您无法将值添加到现有列表的末尾。永远不会更改现有列表。您可以创建一个新的列表,其中添加了一个值。如果你这样做,我不明白你为什么要在由列表和新值组成的末尾添加一对。我怀疑你错了。这是一个获取列表和整数的函数,并将整数添加到列表的末尾。

# let rec addi i list =
    match list with
    | [] -> [i]
    | h :: t -> h :: addi i t
  ;;
val addi : 'a -> 'a list -> 'a list = <fun>
# let x = [1;2;3];;
val x : int list = [1; 2; 3]
# addi 4 x;;
- : int list = [1; 2; 3; 4]
# x;;
- : int list = [1; 2; 3]
# 

该函数返回一个新列表,其值添加到结尾。原始列表不会更改。

作为旁注,将值添加到列表的前面更加惯用。反复添加到列表的末尾很慢 - 它给出了二次行为。如果您想要其他订单,通常要做的就是将所有内容添加到前面然后反转列表 - 这仍然是线性的。

修改

显然你真的想要一个看起来像这样的函数:

让f a list = list @ [(list,a)]

这实际上不可能,类型不正确。列表可以包含仅一种类型的内容。因此,您可以得出结论,列表t的类型与类型(t, v) list相同,其中v是a的类型。这是一种递归类型,而不是你真正想要使用的东西(恕我直言)。

您实际上可以使用-rectypes

在OCaml中获取此类型
$ ocaml -rectypes
        OCaml version 4.00.0

# let f a list = list @ [(list, a)];;
val f : 'a -> (('b * 'a as 'c) list as 'b) -> 'c list = <fun>
# 

但是(正如我所说)这是我要避免的事情。

修改2

现在我看一下,你的第一个代码示例避免了因为你需要递归类型 指定列表的两个不同副本。在使用相同列表调用函数之前,这些可能是不同的类型。所以函数类型不是递归的。使用同一列表的两个副本调用时,将创建一个类型与列表类型不同的新值。它只能起作用,因为您对不同的值(使用不同的类型)使用相同的名称l。它不适用于真正的程序,你需要一个代表你的列表的类型。

另一方评论:在列表开头添加值的好处在于列表的旧值仍然存在。这是新名单的尾部。这似乎很多更接近你可能想要做的事情。