Functional programming OCaml:提取元组的第n个元素?

Functional programming OCaml:提取元组的第n个元素?,functional-programming,ocaml,Functional Programming,Ocaml,对于列表,您可以进行模式匹配并迭代到第n个元素,但是对于元组,您将如何获取第n个元素?不可能在OCaml中编写这样一个完全通用的函数。了解这一点的一种方法是考虑函数的类型。有两个问题。首先,元组的每个大小都是不同的类型。因此,不能编写访问不同大小元组元素的函数。第二个问题是元组的不同元素可以有不同的类型。列表没有这两个问题,这就是为什么可以使用List.nth 如果您愿意使用一个固定大小的元组,其元素都是相同的类型,那么您可以编写一个函数,如@user2361830所示 更新 如果您确实拥有要通

对于列表,您可以进行模式匹配并迭代到第n个元素,但是对于元组,您将如何获取第n个元素?

不可能在OCaml中编写这样一个完全通用的函数。了解这一点的一种方法是考虑函数的类型。有两个问题。首先,元组的每个大小都是不同的类型。因此,不能编写访问不同大小元组元素的函数。第二个问题是元组的不同元素可以有不同的类型。列表没有这两个问题,这就是为什么可以使用
List.nth

如果您愿意使用一个固定大小的元组,其元素都是相同的类型,那么您可以编写一个函数,如@user2361830所示

更新


如果您确实拥有要通过索引访问的相同类型的值集合,则可能应该使用数组。

由于OCaml元组的长度是该类型的一部分,因此在编译时是已知的(并且是固定的),您可以通过对元组进行简单的模式匹配来获得第n项。出于同样的原因,提取“任意长度元组”的第n个元素的问题在实践中不会出现——这样的“元组”无法在OCaml的类型系统中表示

您可能仍然不希望每次需要投影元组时都写出一个模式,并且没有任何东西阻止您生成函数
get_1_1
get_i_j
。。。从
j
-元组中提取
i
-第th个元素,用于代码中出现的
i
j
的任何可能组合,例如

let get_1_1 (a) = a
let get_1_2 (a,_) = a
let get_2_2 (_,a) = a
let get_1_3 (a,_,_) = a
let get_2_3 (_,a,_) = a
...
不一定漂亮,但可能

注意:之前我曾声称OCaml元组的长度最多为255,您只需一次性生成所有可能的元组投影即可。正如@Virgile在评论中指出的,这是不正确的——元组可能是巨大的。这意味着预先生成所有可能的元组投影函数是不切实际的,因此上面的限制“发生在代码中”;博士停止尝试直接访问t-uple的第n个元素,并使用记录或数组,因为它们允许随机访问。 您可以通过使用值解构来解包t-uple,通过
let
构造、
match
构造或函数定义来获取第n个元素:

let ivuple = (5, 2, 1, 1)

let squared_sum_let =
  let (a,b,c,d) = ivuple in
  a*a + b*b + c*c + d*d

let squared_sum_match =
  match ivuple with (a,b,c,d) -> a*a + b*b + c*c + d*d

let squared_sum_fun (a,b,c,d) =
  a*a + b*b + c*c + d*d
匹配
-构造在这里没有优于
let
-构造的优点,它只是为了完整性而被包括在内

不要使用三元组,不要 只有少数情况下,使用t-uples表示类型才是正确的做法。大多数情况下,我们选择t-uple是因为我们懒得定义类型,我们应该将访问t-uple的第n个字段或迭代t-uple的字段的问题解释为是时候切换到适当类型的严重信号

t-uples有两种自然的替代品:记录和数组

何时使用记录 我们可以将一个记录视为一个t-uple,它的条目被标记,因此,如果我们想直接访问它们,它们肯定是t-uple最自然的替代品

type ivuple = {
  a: int;
  b: int;
  c: int;
  d: int;
}
然后,我们通过写入
x.a
直接访问
i类型的
x
值的
a
字段。请注意,记录很容易通过修改进行复制,如
让y={x,d=0}
中所示。没有自然的方法来迭代记录的字段,主要是因为记录不需要是同构的

何时使用数组 一个大的齐次值集合由一个数组充分表示,该数组允许直接访问、迭代和折叠。一个可能带来的不便是数组的大小不是其类型的一部分,但对于固定大小的数组,可以通过引入私有类型(甚至抽象类型)轻松避免这一问题。我在“OCaml编译器检查向量长度”的问题中描述了这种技术的一个示例

关于浮动拳击的注记 在t-uple中使用浮点时,在仅包含浮点的记录和数组中,这些浮点将被解除绑定。因此,当在数值计算中从一种类型更改为另一种类型时,我们不应注意到任何性能修改


看这本书。
²Large在4附近开始。

这里有一个函数,它返回执行此操作所需的ocaml函数字符串;)非常有用,我经常使用它

let tup len n = 
if n>=0 && n<len then
    let rec rep str nn = match nn<1 with 
        |true ->""
        |_->str ^ (rep str (nn-1))in
    let txt1 ="let t"^(string_of_int len)^"_"^(string_of_int n)^" tup = match tup with |" ^ (rep "_," n) ^ "a" and
    txt2 =","^(rep "_," (len-n-2)) and
    txt3 ="->a" in
    if n = len-1 then
        print_string (txt1^txt3)
    else
        print_string (txt1^txt2^"_"^txt3)
else raise (Failure "Error") ;;
返回:

let t8_6 tup = match tup with |_,_,_,_,_,_,a,_->a
当然:

val t8_6 : 'a * 'b * 'c * 'd * 'e * 'f * 'g * 'h -> 'g = <fun>
val t8_6:'a*'b*'c*'d*'e*'f*'g*'h->'g=

元组有长度限制,但它远高于255(界限是OCaml块的最大大小,通常为2^22或2^54,请参见示例)。255(实际上是250,因为有些标记是保留的)是求和类型中非常量构造函数的最大数量。Batters具有最多5个元组的模块。除此之外,最好使用记录,因为事情很容易变得混乱。正如下面的答案所暗示的,您使用的元组不正确。这可能是Python/其他语言如何描述和使用它们的产物——在Python中是一个“不可变列表”。您永远不想迭代元组(而是使用数组或列表),如果元组足够大,最好使用记录来命名字段。
val t8_6 : 'a * 'b * 'c * 'd * 'e * 'f * 'g * 'h -> 'g = <fun>