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 ?
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With