问题描述
我想编写一个函数 f
,它接受 n 个参数,其中 n 在运行时确定,并且可能在每次调用该函数时发生变化,例如
假设我们的函数 f
接受一个整数 n,它是 args 的数量,以及 n 个相同类型的 args 并将它们转换为一个列表:
# f 3 'a' 'b' 'c';;
- : char list = ['a'; 'b'; 'c']
# f 2 1 2;;
- : int list = [1; 2]
我想过类似的事情
let f acc n x =
if n = 0
then List.rev (x::acc)
else f [x] (x - 1)
但在这种情况下,由于类型差异,它不起作用。
解决方法
您的要求没有意义,因为无法在运行时动态更改函数的参数数量。通过检查源代码的文本,可以直接看到任何函数调用中的参数数量:
f a b (* Two parameters *)
f a b c (* Three parameters *)
OCaml 中没有动态评估机制(如其他语言的 eval
机制)。这是静态类型的部分含义。
只需将列表传递给函数即可获得想要的效果。
,使用柯里化,你可以做一些类似于可变参数函数的事情,但你必须说服类型检查器。您将无法方便地将函数的数量作为裸整数提供;相反,您可以将 arity 编码为 GADT 的值:
type (_,'r) arity =
| O : ('r,'r) arity
| I : ('f,'r) arity -> (int->'f,'r) arity
编码工作如下:
-
O : ('r,'r) arity
表示“不带参数的函数”的元数并返回'r
; -
I O : (int -> 'r,'r) arity
表示接受int
然后返回'r
的函数的元数; -
I (I O) : (int -> int -> 'r,'r) arity
表示接受两个int
然后返回一个'r
的函数的元数; -
I (I (I O)) : (int -> int -> int -> 'r,'r) arity
是一个函数的元数,它接受三个int
然后返回一个'r
; - 等
不是将 3
作为第一个参数传递给假设的可变参数函数,而是传递 I (I (I O))
。该值描述了函数应该采用的参数序列(一个 int,然后一个 int,然后一个 int,然后返回)。然后该函数将递归执行,破坏(检查)此描述以决定下一步做什么您可以实现构建其所有参数列表的示例函数,如下所示:
let rec f_aux : type f. int list -> (f,int list) arity -> f =
fun acc arity ->
begin match arity with
| O -> List.rev acc
| I a -> fun x -> f_aux (x :: acc) a
end
let f arity = f_aux [] arity
# f (C(C(C O))) ;;
- : int -> int -> int -> int list = <fun>
# f (C(C(C O))) 111 222 333 ;;
- : int list = [111; 222; 333]
与 GADT 一样,类型推断是不够的,您必须使用预期类型对定义进行注释,包括显式通用量化(type f. …
,其中 f
是被量化的类型变量) .
上面定义的 GADT 只能描述处理 int
的可变参数函数,但请注意,您可以轻松扩展它以允许更多类型的参数(当然,您应该调整您的可变参数函数,以便它们处理这些额外的可能性):
type (_,'r) arity
| B : ('f,'r) arity -> (bool->'f,'r) arity
| C : ('f,'r) arity -> (char->'f,'r) arity
| S : ('f,'r) arity -> (string->'f,'r) arity
(* etc. *)
let rec g_aux : type f. string -> (f,string) arity -> f =
fun acc arity ->
begin match arity with
| O -> acc
| I a -> fun x -> g_aux (acc ^ string_of_int x) a
| B a -> fun x -> g_aux (acc ^ if x then "true" else "false") a
| C a -> fun x -> g_aux (acc ^ String.make 1 x) a
| S a -> fun x -> g_aux (acc ^ x) a
(* etc. *)
end
let g arity = g_aux "" arity
# g (S(I(S(B(C O))))) ;;
- : string -> int -> string -> bool -> char -> string = <fun>
# g (S(I(S(B(C O))))) "Number " 42 " is prime. I swear,it’s " true '!' ;;
- : string = "Number 42 is prime. I swear,it’s true!"
事实上,这就是 OCaml 中实现漂亮打印的本质:当你写 Printf.printf "%s%b" …
时,格式字符串实际上不是一个 string
,它是由某些非常复杂的 GADT 类型值的编译器,例如 (_,_,_) format6
(6 个类型参数!)。您也可以手动构建 GADT 值(不要)。这种语法糖是编译器为漂亮打印所做的唯一魔法,其他一切都适用于标准语言功能。
好吧,我们有一个有效的系统,至少它可以进行类型检查。除非编译器给你糖,否则语法并不漂亮。更重要的是,arities 在 static 类型系统中进行编码和检查,这意味着它们在编译时是已知的。您不能(或者至少很难安全地)在运行时动态地读取一个 arity 作为程序的输入。
实际问题是:为什么您实际上需要这样做,而不是仅仅使用列表?除了语法上的方便之外,它没有带来任何好处。