EDIT: I'm sorry everyone, I thought my small examle was complete, turns out it's not. I made a new one that really should be!
As soon as I use a formatter as parameter to Scanf or Printf functions the formatter type gets bound to a in- or output-channel respectively. Is there a way to have a function take a formatter (or string) and use that as a formatter for both printing and reading?
let fmt = format_of_string "%d,%d";;
Scanf.sscanf "2,2" fmt (fun x y -> x,y);;
fmt;;
gives
- : (int -> int -> int * int, Scanf.Scanning.scanbuf, '_a, (int -> int -> int * int) -> int * int, (int -> int -> int * int) -> int * int, int * int) format6 = <abstr>
Which means a subsequent Printf.printf fmt 1 2;;
gives a type error.
This goes for every combination of format_of_string
and Scanf.format_from_string
like functions I've tried.
Example:
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;;
Gives
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
For the last line of the both
function, which seems to make sense,
but if I remove the both
function from the module, I can call read
and print
with the same format string (same variable as parameter) and it just works.
So, with hopes that you guys haven't given up on me yet; how do I get around that? neither eta-expansion nor type annotation seems to work in this case?
You're hitting the infamous value restriction:
# let fmt = format_of_string "%d,%d";;
val fmt : (int -> int -> '_a, '_b, '_c, '_d, '_d, '_a) format6 = <abstr>
Those '_a
, '_b
, '_c
, '_d
mean "types to be determined as soon as we can". They are not parameters, i.e., fmt
is not a polymorphic value. In contrast, an empty list is polymorphic:
# let emptiness = [] ;;
val emptiness : 'a list = []
Now we have 'a
and not '_a
. But maybe you already know all this. The point is that when we apply Printf.printf
to fmt
, its type gets fixed as
(int -> int -> unit, out_channel, unit, unit, unit, unit) format6
but when we apply Scanf.sscanf
to fmt
its type gets fixed as
(int -> int -> int * int, Scanf.Scanning.in_channel, '_a,
(int -> int -> int * int) -> int * int,
(int -> int -> int * int) -> int * int, int * int)
format6
These two types are not compatible, and because fmt
is not polymorphic, you cannot use it both ways. The solution is simply to have two copies, fmt_in
and fmt_out
. Is there something unacceptable with that solution?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With