您可以使用带有静态解析类型参数的内联函数来根据上下文生成不同的类型。
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 时使编译器或开发人员崩溃。