Why does this behavior occur?
# Printf.sprintf ("Foo %d %s") 2 "bar";;
- : string = "Foo 2 bar"
# Printf.sprintf ("Foo %d" ^ " %s") 2 "bar";;
Printf.sprintf ("Foo %d" ^ " %s") 2 "bar";;
Error: This expression has type string but an expression was expected of type
('a -> 'b -> 'c, unit, string) format =
('a -> 'b -> 'c, unit, string, string, string, string) format6
I would expect that the string concatenation would be evaluated first, so everything will proceed as normal. Does this have to do with the type system trickery that Printf employs?
Yes, it has to do with type system trickery. If you want to create a format string you need to use the (^^) operator:
# Printf.sprintf ("Foo %d" ^^ " %s") 2 "bar";;
- : string = "Foo 2 bar"
I'm not deeply schooled in this trickery, but I believe that the compiler is willing to promote a string constant to a printf format if the typing context calls for it. However, the result of ("Foo %d" ^ " %s")
is not a string constant, so it doesn't get promoted. The (^^) operator creates a typing context where both operands can be promoted if they are string constants.
You can see why it would have to be a string constant: otherwise the associated types (of the values to be printed) can't be determined.
The problem occurs much more widely than just with the ^
operator. Basically, the OCaml compiler needs to know that your format string is a literal string, and the literal string needs to be known at compile time. Or else, OCaml cannot cast your string at compile time to this BLAHBLAH format6
type. The Printf
module works correctly only with format strings that are completely known at compile time, or with format strings that are already cast to the BLAHBLAH format
type.
Generally you can solve this problem by using the ^^
operator and by explicitly casting all literal strings to the BLAHBLAH format
type before using those strings in your code.
Here is another example:
# Printf.sprintf (if true then "%d" else "%d ") 2;;
Error: This expression has type string but an expression was expected of type
('a -> 'b, unit, string) format =
('a -> 'b, unit, string, string, string, string) format6
(* define a type abbreviation for brevity *)
# type ('a,'b) fformat = ('a ->'b, unit, string) format;;
type ('a, 'b) fformat = ('a -> 'b, unit, string) format
# Printf.sprintf (if true then ("%d":('a,'b)fformat) else ("%d ":('a,'b)fformat)) 2;;
- : string = "2"
The OCaml system cannot recognize that if ... then "a" else "b"
can be cast to BLAHBLAH format
. If you cast each literal string yourself to BLAHBLAH format
, then everything works. (Note: it does not work if you try to cast the entire if/then/else
to BLAHBLAH format
, since OCaml cannot verify that your string is a literal.)
The origin of the problem is the requirement of type safety: OCaml requires that there is an argument of the correct type for every %d
and %s
etc., and guarantees this at compile time. You cannot guarantee type safety with Printf
unless the entire format string is known at compile time. Therefore, it is impossible to use Printf
with a format string computed via a complicated algorithm, e.g., by selecting %s
and %d
at random.
When we use if/then/else
to compute the format string, then OCaml things, oh gee, this is a complicated algorithm, and it is hopeless to verify type safety at compile time. The ^^
operator knows about BLAHBLAH format
types and produces the correct result when concatenating format strings. But if/then/else
does not know about BLAHBLAH format
, and there is no built-in alternative to if/then/else
(but I guess you could define such a thing youself).
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