F# casting and generics
Trying to learn some F# and I've run into a couple of hangups.
Here's the code:
#light
module HtmlModule
type HtmlString(text:string) =
override x.ToString() = text
type HtmlAttribute(key:string, value:string) =
inherit HtmlString(key + "=\"" + value + "\"");
type HtmlElement(tag: string, ?contents:list<'a> when 'a :> HtmlString) =
inherit HtmlString("")
let innerContents = defaultArg contents []
let castToHtmlAttribute (hs:HtmlString) : Option<HtmlAttribute> =
match hs with
| :? HtmlAttribute as temp -> Some(temp)
| _ -> None
member x.tag = tag
member x.contents = innerContents |> List.filter(fun x -> (castToHtmlAttribute x).IsNone)
member x.attributes = innerContents |> List.map(fun x -> (castToHtmlAttribute x)) |> List.filter(fun x-> x.IsSome) |> List.map(fun x->x.Value)
override x.ToString() =
let sb = System.Text.StringBuilder()
sb.Append("<" + x.tag)
for att in x.attributes do
sb.Append(" " + att.ToString())
sb.Append(">")
for item in x.contents do
sb.Append(item.ToString())
sb.Append("</" + x.tag + ">")
sb.ToString()
let element tag contents = new HtmlElement(tag, contents)
let div contents = element "div" contents
let p contents = element "p" contents
let text text = new HtmlString(text)
let attr key value = new HtmlAttribute(key, value)
let classes value = attr "class" value
and a quick console program to show the problem:
#light
open HtmlModule
let stuff = [for a in 1 .. 10 do yield p [text ("some String " + a开发者_如何学Go.ToString())]]
let elem = div [
attr "class" "someClass";
text "This is some inner text";
div stuff;
]
let result = elem.ToString()
printfn "%A" result
the compiler will not allow the the line
div stuff;
It states:
"Error 7 Type mismatch. Expecting a HtmlString list but given a HtmlElement list The type 'HtmlString' does not match the type 'HtmlElement'"
This problem can be tracked to the castToHtmlAttribute
method signature
let castToHtmlAttribute (hs:HtmlString) : Option<HtmlAttribute> =
If I make this line more generic like so:
let castToHtmlAttribute (hs:'a when 'a :> HtmlString) : Option<HtmlAttribute> =
I get a new error:
"Error 2 This runtime coercion or type test from type 'a to HtmlAttribute involves an indeterminate type based on information prior to this program point. Runtime type tests are not allowed on some types. Further type annotations are needed."
Any help is appreciated.
The diagnostic here is not great.
You can fix the code like so:
let stuff = [for a in 1 .. 10 do
yield (p [text ("some String " + a.ToString())]) :> HtmlString]
where I've inserted an explicit upcast to the base type.
Expecting a HtmlString list but given a HtmlElement list The type 'HtmlString' does not match the type 'HtmlElement'"
This states the problem quite well. For F#, a List<HTMLElement>
is not a List<HTMLString>
like a List<Orange>
is no List<Fruit>
.
If it were, you could write
Apple :: oranges
Since this doesn't make any sense, F# expects you to cast downcast explicitly. Just tell div
to work with a generic list.
In short, you can change the type HTMLString list
to a polymorphic #HTMLString list
or cast the arguments or stuff
by hand.
精彩评论