thinkbeforecoding
thinkbeforecoding

Reputation: 6778

Statically resolved types with Records

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

Answers (2)

Daniel
Daniel

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

Gene Belitski
Gene Belitski

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

Related Questions