【问题标题】:F# printf compiler magicF# printf 编译器魔法
【发布时间】:2013-06-14 13:08:58
【问题描述】:

在 F# 中,编译器显然做了一些魔法来完成这项工作:

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

它是如何做到的?有什么方法可以从语言中实现类似的行为?

【问题讨论】:

    标签: .net compiler-construction f#


    【解决方案1】:

    遗憾的是,这一点“魔法”(正如您所说的那样)被硬编码到 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
    

    P.S.:我很久以前写过这个,所以我不记得为什么有mkIntmkInt32。这可能是必要的,也可能不是 - 但我确实记得这段代码有效。

    【讨论】:

    【解决方案2】:

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

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

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

    PrintfTypeProvider<"%i %i">.Apply
    

    例如,返回一个int -&gt; int -&gt; string 类型的值,但如果您愿意,您也可以以相当任意的方式扩展逻辑。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-18
        • 2021-12-01
        相关资源
        最近更新 更多