7

間もなく、私と私の腕の兄弟であるJoelは、バージョン0.9のWingBeatsをリリースします。これはF#で記述された内部DSLです。これを使用すると、XHTMLを生成できます。インスピレーションの源の1つは、OcsigenフレームワークのXHTML.Mモジュールです。私はOCaml構文に慣れていませんが、XHTML.Mは、要素の属性と子が有効な型であるかどうかをどういうわけか静的にチェックすることを理解しています。

F#で同じことを静的にチェックすることはできませんでしたが、誰かがそれを行う方法について何か考えがあるのではないかと思います。

私の最初の素朴なアプローチは、XHTMLの各要素タイプをユニオンケースとして表すことでした。ただし、残念ながら、XHTML.Mのように、パラメーター値として有効なケースを静的に制限することはできません。

次に、インターフェイス(各要素タイプは有効な親ごとにインターフェイスを実装します)とタイプ制約を使用しようとしましたが、ソリューションの使用が面倒になるような方法で明示的なキャストを使用せずに機能させることはできませんでした。そして、それはとにかくエレガントな解決策のようには感じませんでした。

今日はコードコントラクトを見てきましたが、F#Interactiveと互換性がないようです。Alt + Enterキーを押すと、フリーズします。

私の質問をより明確にするためだけに。同じ問題の非常に単純な人工的な例を次に示します。

type Letter = 
    | Vowel of string
    | Consonant of string
let writeVowel = 
    function | Vowel str -> sprintf "%s is a vowel" str

writeVowelが静的に母音のみを受け入れ、上記のように実行時にチェックしないようにします。

どうすればこれを達成できますか?誰かが何か考えを持っていますか?それを行うための賢い方法がなければなりません。ユニオンの場合ではない場合、おそらくインターフェースを使用しますか?私はこれに苦労しましたが、箱の中に閉じ込められており、その外を考えることはできません。

4

6 に答える 6

4

そのライブラリは、F#では使用できないO'Camlのポリモーフィックバリアントを使用しているようです。残念ながら、F#でそれらをエンコードする忠実な方法もわかりません。

「ファントムタイプ」を使用する可能性もありますが、扱っているコンテンツのさまざまなカテゴリの数を考えると、これは扱いにくいものになる可能性があります。母音の例を処理する方法は次のとおりです。

module Letters = begin
  (* type level versions of true and false *)
  type ok = class end
  type nok = class end

  type letter<'isVowel,'isConsonant> = private Letter of char

  let vowel v : letter<ok,nok> = Letter v
  let consonant c : letter<nok,ok> = Letter c
  let y : letter<ok,ok> = Letter 'y'

  let writeVowel (Letter(l):letter<ok,_>) = sprintf "%c is a vowel" l
  let writeConsonant (Letter(l):letter<_,ok>) = sprintf "%c is a consonant" l
end

open Letters
let a = vowel 'a'
let b = consonant 'b'
let y = y

writeVowel a
//writeVowel b
writeVowel y
于 2010-05-11T21:02:03.570 に答える
2

厳密に言えば、コンパイル時に何かを区別したい場合は、異なるタイプを指定する必要があります。あなたの例では、2種類の文字を定義すると、その種類Letterは最初の文字または2番目の文字のいずれかになります。

これは少し面倒ですが、おそらくそれがあなたが望むものを達成するための唯一の直接的な方法です:

type Vowel = Vowel of string
type Consonant = Consonant of string
type Letter = Choice<Vowel, Consonant>

let writeVowel (Vowel str) = sprintf "%s is a vowel" str
writeVowel (Vowel "a") // ok
writeVowel (Consonant "a") // doesn't compile

let writeLetter = function
  | Choice1Of2(Vowel str) -> sprintf "%s is a vowel" str
  | Choice2Of2(Consonant str) -> sprintf "%s is a consonant" str

タイプは、最初のChoiceタイプの値または2番目のタイプの値のいずれかを格納できる単純な識別された共用体です。独自の識別された共用体を定義できますが、共用体の場合の適切な名前を思い付くのは少し難しいです(入れ子のため)。

コードコントラクトを使用すると、値に基づいてプロパティを指定できます。この場合は、より適切です。(F#アプリケーションを作成するときに)F#で動作するはずですが、F#と統合した経験はありません。

数値タイプの場合、測定単位を使用することもできます。これにより、タイプに追加情報を追加できます(たとえば、数値にタイプがあるfloat<kilometer>場合)が、これはでは使用できませんstringvowelそうである場合は、測定単位andconsonantとwrite string<vowel>andを定義できますが、測定string<consonant>単位は主に数値アプリケーションに焦点を合わせています。

したがって、おそらく最良のオプションは、場合によってはランタイムチェックに依存することです。

[編集] OCamlの実装に関する詳細を追加するには-OCamlでこれを可能にする秘訣は、構造的サブタイピングを使用することだと思います。つまり、一部のメンバーとの識別された結合を定義できます(例:のみVowel)、さらにメンバーが多い別の(VowelおよびConsonant)。

値を作成すると、Vowel "a"いずれかの型をとる関数の引数として使用できますが、値Consonant "a"は2番目の型をとる関数でのみ使用できます。

.NETは構造サブタイピングをネイティブにサポートしていないため、これをF#に簡単に追加することはできません(.NET 4.0でいくつかのトリックを使用することは可能かもしれませんが、コンパイラーが行う必要があります)。ですから、私はあなたの問題を理解していることは知っていますが、それをどのように解決するかについては良い考えがありません。

構造サブタイピングのいくつかの形式は、F#の静的メンバー制約を使用して実行できますが、識別された共用体のケースはF#の観点からは型ではないため、ここでは使用できないと思います。

于 2010-05-11T19:55:08.973 に答える
2

私の謙虚な提案は、型システムが「X」の静的チェックを簡単にサポートしない場合、「X」を静的にチェックしようとするばかげたゆがみを経験しないことです。実行時に例外をスローするだけです。空は落ちません、世界は終わりません。

静的チェックを取得するためのばかげたゆがみは、APIを複雑にし、エラーメッセージを判読できなくし、継ぎ目で他の劣化を引き起こすという犠牲を払って発生することがよくあります。

于 2010-05-12T01:12:58.810 に答える
2

静的に解決された型パラメーターを持つインライン関数を使用して、コンテキストに応じて異なる型を生成できます。

let inline pcdata (pcdata : string) : ^U = (^U : (static member MakePCData : string -> ^U) pcdata)
let inline a (content : ^T) : ^U = (^U : (static member MakeA : ^T -> ^U) content)        
let inline br () : ^U = (^U : (static member MakeBr : unit -> ^U) ())
let inline img () : ^U = (^U : (static member MakeImg : unit -> ^U) ())
let inline span (content : ^T) : ^U = (^U : (static member MakeSpan : ^T -> ^U) content)

br関数を例にとってみましょう。タイプ^Uの値が生成され、コンパイル時に静的に解決されます。これは、^Uに静的メンバーMakeBrがある場合にのみコンパイルされます。以下の例では、A_Content.BrまたはSpan_Content.Brのいずれかを生成できます。

次に、合法的なコンテンツを表す一連のタイプを定義します。それぞれが、受け入れるコンテンツの「Make」メンバーを公開します。

type A_Content =
| PCData of string
| Br
| Span of Span_Content list
        static member inline MakePCData (pcdata : string) = PCData pcdata
        static member inline MakeA (pcdata : string) = PCData pcdata
        static member inline MakeBr () = Br
        static member inline MakeSpan (pcdata : string) = Span [Span_Content.PCData pcdata]
        static member inline MakeSpan content = Span content

and Span_Content =
| PCData of string
| A of A_Content list
| Br
| Img
| Span of Span_Content list
    with
        static member inline MakePCData (pcdata : string) = PCData pcdata
        static member inline MakeA (pcdata : string) = A_Content.PCData pcdata
        static member inline MakeA content = A content
        static member inline MakeBr () = Br
        static member inline MakeImg () = Img
        static member inline MakeSpan (pcdata : string) = Span [PCData pcdata]
        static member inline MakeSpan content = Span content

and Span =
| Span of Span_Content list
        static member inline MakeSpan (pcdata : string) = Span [Span_Content.PCData pcdata]
        static member inline MakeSpan content = Span content

その後、値を作成できます...

let _ =
    test ( span "hello" )
    test ( span [pcdata "hello"] )
    test (
        span [
            br ();
            span [
                br ();
                a [span "Click me"];
                pcdata "huh?";
                img () ] ] )

そこで使用されるテスト関数はXMLを出力します...このコードは、値が適切に機能することを示しています。

let rec stringOfAContent (aContent : A_Content) =
    match aContent with
    | A_Content.PCData pcdata -> pcdata
    | A_Content.Br -> "<br />"
    | A_Content.Span spanContent -> stringOfSpan (Span.Span spanContent)

and stringOfSpanContent (spanContent : Span_Content) =
    match spanContent with
    | Span_Content.PCData pcdata -> pcdata
    | Span_Content.A aContent ->
        let content = String.concat "" (List.map stringOfAContent aContent)
        sprintf "<a>%s</a>" content
    | Span_Content.Br -> "<br />"
    | Span_Content.Img -> "<img />"
    | Span_Content.Span spanContent -> stringOfSpan (Span.Span spanContent)

and stringOfSpan (span : Span) =
    match span with
    | Span.Span spanContent ->
        let content = String.concat "" (List.map stringOfSpanContent spanContent)
        sprintf "<span>%s</span>" content

let test span = printfn "span: %s\n" (stringOfSpan span)

出力は次のとおりです。

span: <span>hello</span>

span: <span><br /><span><br /><a><span>Click me</span></a>huh?<img /></span></span>

エラーメッセージは妥当なようです...

test ( div "hello" )

Error: The type 'Span' does not support any operators named 'MakeDiv'

Make関数と他の関数はインラインであるため、生成されたILは、型安全性を追加せずにこれを実装した場合に手動でコーディングするものとおそらく似ています。

同じアプローチを使用して属性を処理できます。

ブライアンが曲芸師の解決策がそうするかもしれないと指摘したように、私はそれが継ぎ目で劣化するかどうか疑問に思います。(これは曲芸師としてカウントされますか?)または、すべてのXHTMLを実装するまでにコンパイラーまたは開発者を溶かしてしまうかどうか。

于 2010-05-14T02:42:48.923 に答える
1

クラス?

type Letter (c) =
    member this.Character = c
    override this.ToString () = sprintf "letter '%c'" c

type Vowel (c) = inherit Letter (c)

type Consonant (c) = inherit Letter (c)

let printLetter (letter : Letter) =
    printfn "The letter is %c" letter.Character

let printVowel (vowel : Vowel) =
    printfn "The vowel is %c" vowel.Character

let _ =
    let a = Vowel('a')
    let b = Consonant('b')
    let x = Letter('x')

    printLetter a
    printLetter b
    printLetter x

    printVowel a
//    printVowel b  // Won't compile

    let l : Letter list = [a; b; x]
    printfn "The list is %A" l
于 2010-05-12T18:15:12.003 に答える
1

すべての提案をありがとう!問題の解決策を考え出すように誰かを刺激する場合に備えて、以下はDSLWingBeatsで記述された単純なHTMLページです。スパンは体の子です。これは有効なHTMLではありません。コンパイルされなかったらいいのにと思います。

let page =
    e.Html [
        e.Head [ e.Title & "A little page" ]
        e.Body [
            e.Span & "I'm not allowed here! Because I'm not a block element."
        ]
    ]

それとも、私たちが考えていなかった、それをチェックする他の方法はありますか?私たちは実用的です!考えられるすべての方法を調査する価値があります。Wing Beatsの主な目標の1つは、プログラマーをガイドする(X)Htmlエキスパートシステムのように動作させることです。プログラマーが、知識の不足や不注意なミスのためではなく、選択した場合にのみ無効な(X)Htmlを生成するようにしたいのです。

属性を静的にチェックするためのソリューションがあると思います。次のようになります。

module a = 
    type ImgAttributes = { Src : string; Alt : string; (* and so forth *) }
    let Img = { Src = ""; Alt = ""; (* and so forth *) }
let link = e.Img { a.Img with Src = "image.jpg"; Alt = "An example image" }; 

長所と短所がありますが、機能するはずです。

さて、誰かが何かを思いついたら、私たちに知らせてください!

于 2010-05-13T11:12:09.713 に答える