F# では、コンパイラは明らかにこれを機能させるためにいくつかの魔法を行っています。
printfn "%i %i" 6 7 ;; // good
printfn "%i %i" 6 7 8;; // error
これはどのように行われていますか?言語内から同様の動作を実現する方法はありますか?
F# では、コンパイラは明らかにこれを機能させるためにいくつかの魔法を行っています。
printfn "%i %i" 6 7 ;; // good
printfn "%i %i" 6 7 8;; // error
これはどのように行われていますか?言語内から同様の動作を実現する方法はありますか?
魔法は、文字列リテラルから type への暗黙的な変換にありますPrintfFormat<_,_,_,_>
。たとえば、printf
は type の引数を取りますがTextWriterFormat<'a>
、これは実際には の単なるエイリアスですPrintfFormat<'a,System.IO.TextWriter,unit,unit>
。
この暗黙的な変換の魔法は、言語内で簡単にエミュレートすることはできませんが、関数のファミリについて特別なことは何もありません。型の引数を取り、それらを文字列リテラルで問題なく使用するprintf
独自の関数を作成できます。PrintfFormat<_,_,_,_>
文字列からの暗黙的な変換を拡張する方法はありませんが、型プロバイダーを使用することも 1 つの方法です。型プロバイダーを作成するのは非常に簡単です。
PrintfTypeProvider<"%i %i">.Apply
たとえば、 type の値を返しますがint -> int -> string
、必要に応じてかなり任意の方法でロジックを拡張することもできます。
悲しいことに、このちょっとした "魔法" (と呼ばれる) は、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"
、型チェックと print を行います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
mkInt
PS: これはずっと前に書いたので、なぜ両方とそこにあるのか思い出せませんmkInt32
。必要かもしれませんし、そうでないかもしれませんが、このコードが機能したことを覚えています。