2012-05-02 11 views
11

为什么会发生这种行为?OCaml Printf.sprintf

# 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使用的类型系统欺骗有关?

回答

17

是的,它与类型系统欺骗有关。如果你想创建一个格式字符串,你需要使用(^^)操作:

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

我不是深深的这种挂羊头卖狗肉受过教育,但我相信,编译器愿为推动一个字符串常量如果键入上下文调用printf格式。但是,("Foo %d"^" %s")的结果不是字符串常量,所以不会升级。 (^^)运算符创建一个输入上下文,其中两个操作数可以被提升,如果它们是字符串常量的话。

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

+1

一个很好的解释,谢谢。一个小点,我觉得你的表述“(^^)操作符创建一个输入上下文..”不清楚;这个运算符在里面没有魔术,只是输入'... format - > ... format - > ... format',并且类型推断做了检查它的参数是这种类型的常量字符串的工作。 – gasche

+1

gasche是对的。需要理解的是,当你给“Print。* printf”一个字符串时,你不给它一个'Pervasives.string'类型的值,你给它一个'Pervasives.format6'类型的值(这不是等于'Pervasives.string')。 –

+0

对,我应该说,(^^)有gasche给出的类型。这部分绝对没有魔法。唯一的魔法(据我所知)是在将字符串常量提升为适当的'format'类型。 –

8

这个问题的发生远远超过^运营商。基本上,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等的正确类型的参数,并且保证这在编译时间。除非在编译时间已知整个格式字符串,否则不能保证类型安全。因此,不可能将Printf与通过复杂算法计算的格式串一起使用,例如通过随机选择%s%d

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