Dax Fohl
Dax Fohl

Reputation: 10781

Generics in record "with" syntax

If I've got a record with a generic field, is there any way to mimic the convenient with syntax when changing the generic?

i.e. if I have

type User<'photo> = // 'photo can be Bitmap or Url
  { first: string; last: string; address: string; phone: string; photo: 'photo }

I want to be able to write something like

let loadUser(user: User<Url>): User<Bitmap> =
  { user with
    photo = download user.photo }

But it looks like I've got to write this instead.

let loadUser(user: User<Url>): User<Bitmap> =
  { first = user.first
    last = user.last
    address = user.address
    phone = user.phone
    photo = download user.photo }

Is there a way to get the first syntax?

Upvotes: 3

Views: 210

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243106

Sadly the { with .. } syntax does not handle the case when the type changes (though I think this could be added, so feel free to open a suggestion at the F# user voice page!)

Adding map function works, but another alternative is to define With method on the type. This can take optional parameters for non-generic fields. For the generic field, you cannot quite do this (because it would unify the types), but you can have an overload:

type User<'TPhoto> = 
  { Name : string
    Photo : 'TPhoto }
  member x.With(photo, ?name) =
    { Name = defaultArg name x.Name; Photo = photo }
  member x.With(?name) =
    { Name = defaultArg name x.Name; Photo = x.Photo }

The first method is generic and returns User with a photo of another type. The second method does not change the photo and so it is non-generic. You can then use it like this:

let t = { Name = "Tomas"; Photo = 0 }

t.With(photo="img")   // Change type of photo
t.With(name="Tomáš")  // Change just the name
t.With(photo="img", name="Test") // Change everything

Upvotes: 4

Random Dev
Random Dev

Reputation: 52290

not directly but you can make your User into a functor (for the photo part):

let fmap (f : 'a -> 'b) (user: User<'a>): User<'b> =
  { first   = user.first
    last    = user.last
    address = user.address
    phone   = user.phone
    photo   = f user.photo }

once and write (for example):

let loadUser (user : User<Url>) : User<Bitmap> =
    fmap download user

also you could rename fmap into withPhoto if you prefer let loadUser = withPhoto download :D

now it might be a bit strange that the photo part is any kind of data/type so I would think about renamig this part just into value - but that's just me

Upvotes: 5

Related Questions