3

In F#, the compiler is clearly doing some magic to make this work:

printfn "%i %i" 6 7 ;; // good
printfn "%i %i" 6 7 8;; // error

How is it doing this? and is there any way to achieve a similar behavior, from within the language?

4

3 回答 3

4

可悲的是,这一点“魔法”(正如您所说的那样)被硬编码到 F# 编译器中。您可以扩展编译器,但结果将是非标准的 F#。

这是处理该问题的特定代码(它不太可读,但这就是 F# 编译器的编写方式):

and TcConstStringExpr cenv overallTy env m tpenv s  =

    if (AddCxTypeEqualsTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy cenv.g.string_ty) then 
      mkString cenv.g m s,tpenv
    else 
      let aty = NewInferenceType ()
      let bty = NewInferenceType ()
      let cty = NewInferenceType ()
      let dty = NewInferenceType ()
      let ety = NewInferenceType ()
      let ty' = mkPrintfFormatTy cenv.g aty bty cty dty ety
      if (not (isObjTy cenv.g overallTy) && AddCxTypeMustSubsumeTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy ty') then 
        // Parse the format string to work out the phantom types 
        let aty',ety' = (try Formats.ParseFormatString m cenv.g s bty cty dty with Failure s -> error (Error(FSComp.SR.tcUnableToParseFormatString(s),m)))
        UnifyTypes cenv env m aty aty';
        UnifyTypes cenv env m ety ety';
        mkCallNewFormat cenv.g m aty bty cty dty ety (mkString cenv.g m s),tpenv
      else 
        UnifyTypes cenv env m overallTy cenv.g.string_ty;
        mkString cenv.g m s,tpenv

这是相同的代码,它也支持数字字符串(即printfn "%i %i" ("4" + 2) "5"类型检查和打印6 5):

and TcConstStringExpr cenv overallTy env m tpenv s  =

    if (AddCxTypeEqualsTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy cenv.g.string_ty) then 
      mkString cenv.g m s,tpenv
    elif (AddCxTypeEqualsTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy cenv.g.int_ty) then 
      mkInt cenv.g m (System.Int32.Parse s),tpenv
    elif (AddCxTypeEqualsTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy cenv.g.int32_ty) then 
      mkInt32 cenv.g m (System.Int32.Parse s),tpenv
    else 
      let aty = NewInferenceType ()
      let bty = NewInferenceType ()
      let cty = NewInferenceType ()
      let dty = NewInferenceType ()
      let ety = NewInferenceType ()
      let ty' = mkPrintfFormatTy cenv.g aty bty cty dty ety
      if (not (isObjTy cenv.g overallTy) && AddCxTypeMustSubsumeTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy ty') then 
        // Parse the format string to work out the phantom types 
        let aty',ety' = (try Formats.ParseFormatString m cenv.g s bty cty dty with Failure s -> error (Error(FSComp.SR.tcUnableToParseFormatString(s),m)))
        UnifyTypes cenv env m aty aty';
        UnifyTypes cenv env m ety ety';
        mkCallNewFormat cenv.g m aty bty cty dty ety (mkString cenv.g m s),tpenv
      else 
        UnifyTypes cenv env m overallTy cenv.g.string_ty;
        mkString cenv.g m s,tpenv

PS:我很久以前写的,所以我不记得为什么两者mkInt都有mkInt32。这可能是必要的,也可能不是 - 但我确实记得这段代码有效。

于 2013-06-14T13:18:14.973 回答
4

神奇之处在于从字符串文字到类型的隐式转换PrintfFormat<_,_,_,_>。例如,printf接受一个类型的参数TextWriterFormat<'a>,它实际上只是一个别名PrintfFormat<'a,System.IO.TextWriter,unit,unit>

这种隐式转换的魔力无法在语言中轻松模拟,但printf函数系列并没有什么特别之处——您可以编写自己的函数,接受类型参数PrintfFormat<_,_,_,_>并将它们与字符串文字一起使用,没有任何问题。

虽然无法从字符串扩展隐式转换,但一种替代方法是使用类型提供程序。编写一个类型提供程序会很容易,这样

PrintfTypeProvider<"%i %i">.Apply

例如,返回一个 type 的值int -> int -> string,但如果你愿意,你也可以以相当任意的方式扩展逻辑。

于 2013-06-14T15:19:49.383 回答
1

如果您只想在不输出字符串的情况下对字符串进行此行为,那么sprintf是一个类似的函数,它返回字符串而不是将其打印出来。因此,您可以拥有返回以这种方式进行类型检查格式化的字符串的函数。

于 2013-08-31T19:21:08.330 回答