2012-05-02 16 views
11

この現象はなぜ発生しますか?OCaml Printf.sprintf

# Printf.sprintf ("Foo %d %s") 2 "bar";; 
- : string = "Foo 2 bar" 

# Printf.sprintf ("Foo %d"^" %s") 2 "bar";; 
    Printf.sprintf ("Foo %d"^" %s") 2 "bar";; 
Error: This expression has type string but an expression was expected of type 
     ('a -> 'b -> 'c, unit, string) format = 
      ('a -> 'b -> 'c, unit, string, string, string, string) format6 

文字列連結が最初に評価されるので、すべて正常に処理されると思います。これはPrintfが採用している型システムのトリッキーと関係がありますか?

答えて

17

はい、タイプのシステムトリッキーと関係があります。あなたが(^^)演算子を使用する必要がフォーマット文字列を作成する場合:

# Printf.sprintf ("Foo %d" ^^ " %s") 2 "bar";; 
- : string = "Foo 2 bar" 

を私は深く、この策略で教育を受けていないんだけど、私は、コンパイラは、文字列定数を促進するために喜んであると信じています型付きコンテキストが呼び出す場合は、をprintf形式に変換します。ただし、("Foo %d"^" %s")の結果は文字列定数ではないため、昇格しません。 (^^)演算子は、両方のオペランドが文字列定数であればプロモートできる入力コンテキストを作成します。

なぜそれが文字列定数でなければならないのか分かります。そうでなければ、関連するタイプ(印刷する値のタイプ)を特定できません。

+1

詳細な説明は、ありがとうございます。副次的な点として、私はあなたの定式化を見つける "(^^)演算子は、入力コンテキストを作成する.."不明;この演算子は内部に魔法を持たず、 '... format - > ... format - > ... format'とタイプされ、型推論はそのパラメータがこの型の定数文字列であることをチェックする作業を行います。 – gasche

+1

ガスチェが正しいです。理解しなければならないことは、 'Print。* printf'に文字列を渡すときに、' Pervasives.string'型の値を与えていないと、 'Pervasives.format6'の値を与えていることです。 'Pervasives.string'に等しい)。 –

+0

さて、私はちょうど(^^)にはガスチが与えるタイプがあると言わなければならない。確かにその部分に魔法はない。私が理解している唯一の魔法は、適切な 'format'型の文字列定数の宣伝です。 –

8

この問題は、^演算子の場合よりもはるかに広く発生します。基本的に、OCamlコンパイラは、あなたの書式文字列がリテラル文字列であり、リテラル文字列がコンパイル時に知られている必要があることを知る必要があります。あるいは、OCamlはコンパイル時にこの文字列をBLAHBLAH format6型にキャストできません。 Printfモジュールは、コンパイル時に完全にわかっている書式文字列、またはBLAHBLAH format型に既にキャストされている書式文字列でのみ正しく動作します。

一般的に、あなたは^^演算子を使用して、明示的にコード内でこれらの文字列を使用して前BLAHBLAH formatタイプへのすべてのリテラル文字列をキャストすることによってこの問題を解決することができます。ここで

は別の例である:

# Printf.sprintf (if true then "%d" else "%d ") 2;; 
    Error: This expression has type string but an expression was expected of type 
    ('a -> 'b, unit, string) format = 
     ('a -> 'b, unit, string, string, string, string) format6 
    (* define a type abbreviation for brevity *) 
    # type ('a,'b) fformat = ('a ->'b, unit, string) format;; 
    type ('a, 'b) fformat = ('a -> 'b, unit, string) format 
    # Printf.sprintf (if true then ("%d":('a,'b)fformat) else ("%d ":('a,'b)fformat)) 2;; 
    - : string = "2" 

OCamlのシステムはif ... then "a" else "b"BLAHBLAH formatにキャストできることを認識することはできません。リテラル文字列をそれぞれにキャストしてBLAHBLAH formatにキャストすると、すべてが機能します。 (注:OCamlはあなたの文字列リテラルであることを確認することができないので、BLAHBLAH formatに全体if/then/elseをキャストしようとした場合、それは動作しません。)

問題の起源は、型の安全性の要件である:OCamlはする必要がありあり%d%sなどの正しい型の引数であり、これはコンパイル時にになります。コンパイル時にで書式文字列全体がでない限り、Printfで型の安全性を保証することはできません。従って、Printfを、例えば%s%dをランダムに選択するなど、複雑なアルゴリズムによって計算された書式文字列に使用することは不可能である。

if/then/elseを使用してフォーマット文字列を計算すると、OCamlのようなものは複雑なアルゴリズムであり、コンパイル時に型の安全性を検証することはできません。 ^^演算子はBLAHBLAH format型を認識し、書式文字列を連結するときに正しい結果を生成します。しかし、if/then/elseBLAHBLAH formatを知らず、if/then/elseの組み込みの代替手段はありません(しかし、私はあなた自身でそのようなことを定義することができます)。