Ocaml格式和值限制



EDIT: 我很抱歉大家,我以为我的小例子是完整的,结果不是。我做了一个新的,真的应该!

一旦我使用格式化器作为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_stringScanf.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 "42n";
  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函数,我可以用相同的格式字符串(与参数相同的变量)调用readprint,它只是工作。

所以,希望你们还没有放弃我;我怎么才能避开呢?在这种情况下,展开和类型注释似乎都不起作用。

您遇到了臭名昭著的值限制:

# let fmt = format_of_string "%d,%d";;
val fmt : (int -> int -> '_a, '_b, '_c, '_d, '_d, '_a) format6 = <abstr>

这些'_a, '_b, '_c, '_d表示"类型有待尽快确定"。它们是而不是参数,即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_infmt_out。这个解决方案有什么不可接受的地方吗?

另一个想法是使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 = ()

这是有点笨拙,但也许不是太坏…?

您的示例不遵守值限制,因为它是一个函数应用程序。然而,对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";;

相关内容

  • 没有找到相关文章

最新更新