Reputation: 6778
You probably know the Adapter example below :
type Cat() =
member this.Walk() = printf "Cat walking"
type Dog() =
member this.Walk() = printf "Dog walking"
let inline walk (animal : ^T ) =
(^T : (member Walk : unit -> unit) (animal))
let cat = new Cat()
let dog = new Dog()
walk cat
walk dog
a different version of walk is compiled statically for the Cat and Dog classes.
Then I tried the following :
type Cat = { Name : string }
type Dog = { Name : string }
let inline showName (animal : ^T ) =
let name = (^T : (member Name : string) (animal))
printf "%s" name
let cat = { Name = "Miaou" } : Cat
let dog = { Name = "Waf" } : Dog
showName cat
showName dog
but I get the following compilation error :
The type 'Dog' does not support any operators named 'get_Name'
and the same for class Cat.
But when exploring the generated class for both records, it actually contains this get_Name method for the generated Name property.
Is there a different syntax to access Records fields in statically resolved generics, or is it a F# compiler limitation ?
Upvotes: 3
Views: 354
Reputation: 47904
It may be worth mentioning that record types can implement interfaces.
type INamedObject =
abstract Name : string
type Cat =
{ Name : string }
interface INamedObject with
member this.Name = this.Name
type Dog =
{ Name : string }
interface INamedObject with
member this.Name = this.Name
let showName (namedObject : INamedObject) =
printf "%s" namedObject.Name
You can also do
type Cat = { Name : string }
type Dog = { Name : string }
let showName (animal : obj) =
let name =
match animal with
| :? Cat as cat -> cat.Name
| :? Dog as dog -> dog.Name
| _ -> invalidArg "animal" "Not an animal"
printf "%s" name
Upvotes: 4
Reputation: 10350
Your showName
function implementation applies to standard .NET classes having Name
property. Although neither Cat
nor Dog
are such; instead both are of F# record type.
Despite accessing a record field
looks literally similar to accessing a standard class property
for F# type inference these two cases are completely different.
You have defined two record types with non-unique field name Name
; access to the field Name
of instance cat
of record type Cat
is cat.Name
, similarly for dog
it is dog.Name
. But when you try showName cat
or showName dog
compiler complains on absense of Name
property in these record types, which is expected behavior, because there is no such property in these records.
Addendum:
To illustrate my point I did a slight modification to the original code adding property Nickname
to both Cat
and Dog
:
type Cat = { Name : string } member x.Nickname = x.Name
type Dog = { Name : string } member x.Nickname = x.Name
let inline showName (animal : ^T ) =
let name = (^T : (member Nickname : string) (animal))
printfn "%s" name
let cat = { Name = "Miaou" } : Cat
let dog = { Name = "Waf" } : Dog
showName cat
showName dog
This will happily work.
Take a notice of signature of modified classes: it is now
type Cat =
{Name: string;}
with
member Nickname : string
end
And finally, the compiler will forbid having both a field and a property of a record type similarly named with The member 'Xyzzy' can not be defined because the name 'Xyzzy' clashes with the field 'Xyzzy' in this type or module
message.
Upvotes: 3