编辑:对不起大家,我认为我的小考试已经完成,结果却没有。 我做了一个真的应该是新的!
一旦我使用格式化程序作为Scanf或Printf函数的参数,格式化程序类型就会分别绑定到输入通道或输出通道。 有没有办法让函数采用格式化程序(或字符串)并将其用作打印和读取的格式化程序?
<击> 撞击>
<击>let fmt = format_of_string "%d,%d";;
Scanf.sscanf "2,2" fmt (fun x y -> x,y);;
fmt;;
给出
- : (int -> int -> int * int, Scanf.Scanning.scanbuf, '_a, (int -> int -> int * int) -> int * int, (int -> int -> int * int) -> int * int, int * int) format6 = <abstr>
这意味着后续的Printf.printf fmt 1 2;;
会出现类型错误。
这适用于format_of_string
和Scanf.format_from_string
类似函数的每个组合。
击>
示例:
module Thing = struct
(* Just a helper for file IO *)
type 'a result = Success of 'a | Failure of exn;;
let with_out_file filename fn =
let out_ch = open_out filename in
let res = try Success (fn out_ch) with
exn -> Failure exn in
close_out out_ch;
match res with
| Success a -> a
| Failure a -> raise a;;
(* Uses the format string for writing *)
let print (fmt : ('a, 'b, 'c, 'd, 'e, 'f) format6) fn v =
with_out_file fn (fun x -> Printf.fprintf x fmt v);;
(* Uses the format string for reading *)
let read (fmt : ('a, 'b, 'c, 'd, 'e, 'f) format6) v =
Scanf.sscanf v fmt (fun x -> x);;
(* Where things break *)
let both fmt v =
read fmt "42\n";
print fmt "tfile" v;;
end;;
给出
Error: This expression has type ('a -> 'b, Scanf.Scanning.scanbuf, 'c, ('d -> 'd) -> 'e, ('a -> 'b) -> 'f, 'f) format6 but an expression was expected of type
('a -> 'b, out_channel, unit, unit, unit, unit) format6
Type Scanf.Scanning.scanbuf is not compatible with type out_channel
对于both
函数的最后一行,这似乎有意义,
但是如果我从模块中删除both
函数,我可以使用相同的格式字符串(与参数相同的变量)调用read
和print
,它就可以正常工作。
所以,希望你们还没有放弃我;我怎么解决这个问题? 在这种情况下,eta扩展和类型注释似乎都不起作用吗?
答案 0 :(得分:4)
你正在遭遇臭名昭着的value restriction:
# let fmt = format_of_string "%d,%d";;
val fmt : (int -> int -> '_a, '_b, '_c, '_d, '_d, '_a) format6 = <abstr>
那些'_a
,'_b
,'_c
,'_d
表示&#34;类型,我们可以尽快确定&#34;。它们是 not 参数,即fmt
不是多态值。相反,空列表是多态的:
# let emptiness = [] ;;
val emptiness : 'a list = []
现在我们有'a
而不 '_a
。但也许你已经知道了这一切。关键是,当我们将Printf.printf
应用于fmt
时,其类型将固定为
(int -> int -> unit, out_channel, unit, unit, unit, unit) format6
但是当我们将Scanf.sscanf
应用于fmt
时,其类型会被修复为
(int -> int -> int * int, Scanf.Scanning.in_channel, '_a,
(int -> int -> int * int) -> int * int,
(int -> int -> int * int) -> int * int, int * int)
format6
这两种类型不兼容,并且因为fmt
不是多态的,所以不能两种方式使用它。解决方案只是拥有两个副本fmt_in
和fmt_out
。这个解决方案有什么不可接受的吗?
答案 1 :(得分:4)
另一个想法是使fmt
成为函数而不是实际的格式值:
# let fmt () = format_of_string "%d,%d";;
val fmt : unit -> (int -> int -> 'a, 'b, 'c, 'd, 'd, 'a) format6 = <fun>
# Scanf.sscanf "2,2" (fmt ()) (fun x y -> (x, y));;
- : int * int = (2, 2)
# Printf.printf (fmt ()) 3 4;;
3,4- : unit = ()
这有点笨重,但也许不是太糟糕......?
答案 2 :(得分:4)
您的示例不遵守值限制,因为它是一个函数应用程序。但是,无需调用format_of_string
。 format_of_string
实际上只是类型为
('a, 'b, 'c, 'd, 'e, 'f) format6 -> ('a, 'b, 'c, 'd, 'e, 'f) format6
它的工作原理是在其参数上强制使用类型注释,这会导致文字被解释为格式说明符而不是字符串。
相反,您可以直接自己提供类型注释:
# let fmt : ('a, 'b, 'c, 'd, 'e, 'f) format6 = "%d,%d";;
val fmt : (int -> int -> 'f, 'b, 'c, 'e, 'e, 'f) format6 = <abstr>
请注意,该类型现在是完全多态的,因为表达式现在是一个值。
更新:在OCaml 4.02中,您现在可以将类型注释缩短为:
let fmt : _ format6 = "%d,%d";;