【发布时间】:2013-06-14 13:08:58
【问题描述】:
在 F# 中,编译器显然做了一些魔法来完成这项工作:
printfn "%i %i" 6 7 ;; // good
printfn "%i %i" 6 7 8;; // error
它是如何做到的?有什么方法可以从语言中实现类似的行为?
【问题讨论】:
标签: .net compiler-construction f#
在 F# 中,编译器显然做了一些魔法来完成这项工作:
printfn "%i %i" 6 7 ;; // good
printfn "%i %i" 6 7 8;; // error
它是如何做到的?有什么方法可以从语言中实现类似的行为?
【问题讨论】:
标签: .net compiler-construction f#
遗憾的是,这一点“魔法”(正如您所说的那样)被硬编码到 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.:我很久以前写过这个,所以我不记得为什么有mkInt和mkInt32。这可能是必要的,也可能不是 - 但我确实记得这段代码有效。
【讨论】:
神奇之处在于从字符串文字到类型PrintfFormat<_,_,_,_> 的隐式转换。例如,printf 接受 TextWriterFormat<'a> 类型的参数,它实际上只是 PrintfFormat<'a,System.IO.TextWriter,unit,unit> 的别名。
这种隐式转换的魔力无法在语言中轻松模拟,但 printf 系列函数并没有什么特别之处 - 您可以编写自己的函数,接受 PrintfFormat<_,_,_,_> 类型的参数并将它们与字符串文字没有任何问题。
虽然无法扩展字符串的隐式转换,但一种替代方法是使用类型提供程序。编写一个类型提供程序会很容易,这样
PrintfTypeProvider<"%i %i">.Apply
例如,返回一个int -> int -> string 类型的值,但如果您愿意,您也可以以相当任意的方式扩展逻辑。
【讨论】:
如果您只想在不输出字符串的情况下对字符串进行此行为,那么sprintf 是一个类似的函数,它返回字符串而不是将其打印出来。因此,您可以拥有返回经过类型检查格式化的字符串的函数。
【讨论】: