13

为什么会出现这种行为?

# 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

我希望首先评估字符串连接,所以一切都会正常进行。这是否与 Printf 使用的类型系统技巧有关?

4

2 回答 2

19

是的,它与类型系统技巧有关。如果要创建格式字符串,则需要使用 (^^) 运算符:

# Printf.sprintf ("Foo %d" ^^ " %s") 2 "bar";;
- : string = "Foo 2 bar"

我对这个技巧没有深入了解,但我相信如果键入上下文需要,编译器愿意将字符串常量提升为 printf 格式。但是,结果("Foo %d" ^ " %s")不是字符串常量,因此不会被提升。(^^) 运算符创建一个键入上下文,如果两个操作数都是字符串常量,则可以在其中提升它们。

您可以看到为什么它必须是字符串常量:否则无法确定(要打印的值的)关联类型。

于 2012-05-02T01:19:22.763 回答
10

这个问题发生的范围比操作员要广泛得多^。基本上,OCaml 编译器需要知道您的格式字符串是文字字符串,并且需要在编译时知道文字字符串。否则,OCaml 无法在编译时将您的字符串转换为这种BLAHBLAH format6类型。该Printf模块仅适用于在编译时完全已知的格式字符串,或已转换为该BLAHBLAH format类型的格式字符串。

通常,您可以通过使用运算符并在代码中使用这些字符串之前^^将所有文字字符串显式转换为BLAHBLAH format类型来解决此问题。

这是另一个例子:

  # 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"

OCaml 系统无法识别if ... then "a" else "b"可以转换为BLAHBLAH format. 如果您自己将每个文字字符串BLAHBLAH format转换为,那么一切正常。if/then/else(注意:如果您尝试将整个转换为,则它不起作用BLAHBLAH format,因为 OCaml 无法验证您的字符串是否为文字。)

问题的根源是类型安全的要求:OCaml 要求每个等都有一个正确类型的参数%d,并在编译时%s保证这一点。除非在编译时知道整个格式字符串,否则不能保证类型安全。因此,不可能使用通过复杂算法计算的格式字符串,例如通过随机选择和。PrintfPrintf%s%d

当我们if/then/else用来计算格式字符串时,然后是 OCaml 的东西,哎呀,这是一个复杂的算法,在编译时验证类型安全是没有希望的。^^运算符了解类型并在BLAHBLAH format连接格式字符串时产生正确的结果。但是if/then/else不知道BLAHBLAH format,并且没有内置的替代方法if/then/else(但我想你可以自己定义这样的东西)。

于 2012-05-03T08:03:34.610 回答