3

F# では、コンパイラは明らかにこれを機能させるためにいくつかの魔法を行っています。

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

これはどのように行われていますか?言語内から同様の動作を実現する方法はありますか?

4

3 に答える 3

4

魔法は、文字列リテラルから type への暗黙的な変換にありますPrintfFormat<_,_,_,_>。たとえば、printfは type の引数を取りますがTextWriterFormat<'a>、これは実際には の単なるエイリアスですPrintfFormat<'a,System.IO.TextWriter,unit,unit>

この暗黙的な変換の魔法は、言語内で簡単にエミュレートすることはできませんが、関数のファミリについて特別なことは何もありません。型の引数を取り、それらを文字列リテラルで問題なく使用するprintf独自の関数を作成できます。PrintfFormat<_,_,_,_>

文字列からの暗黙的な変換を拡張する方法はありませんが、型プロバイダーを使用することも 1 つの方法です。型プロバイダーを作成するのは非常に簡単です。

PrintfTypeProvider<"%i %i">.Apply

たとえば、 type の値を返しますがint -> int -> string、必要に応じてかなり任意の方法でロジックを拡張することもできます。

于 2013-06-14T15:19:49.383 に答える
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"、型チェックと 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

mkIntPS: これはずっと前に書いたので、なぜ両方とそこにあるのか思い出せませんmkInt32。必要かもしれませんし、そうでないかもしれませんが、このコードが機能したことを覚えています。

于 2013-06-14T13:18:14.973 に答える