Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OCaml Printf.sprintf

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?

like image 876
Nick Heiner Avatar asked May 02 '12 01:05

Nick Heiner


2 Answers

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.

like image 97
Jeffrey Scofield Avatar answered Nov 10 '22 05:11

Jeffrey Scofield


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).

like image 41
winitzki Avatar answered Nov 10 '22 03:11

winitzki