Reputation: 5394
Lets assume I have a record like:
type Page = { footersize: Size }
what is the correct syntax to change only the Height of the footersize:
{ page with footersize = ??? }
TIA
Edit#1: "Size" is the size of a FrameworkElement, i.e., Size is in the .Net world as Size(width,height) and is a structure, not record. As tried below, this does NOT work:
{p with footersize = {p.footersize with Height = 96 * 0.5}}
Error: This expression was expected to have type 'Size' but here as type Page.
Upvotes: 1
Views: 531
Reputation: 12687
The most direct way is to copy-update the whole object:
let page' =
{ page with
FooterSize = Size(page.FooterSize.Width, 100.)
}
If you have some deeper nesting, it gets a bit harder.
type Page = { FooterSize: Size }
type Document = { Title: string; Page: Page }
let document = { Title = "Fun and Profit"; Page = { FooterSize= Size(10., 10.) } }
To update height, now you have to do:
let document' = { document with Page = { document.Page with FooterSize= Size(document.Page.FooterSize.Width, 100.) }}
And that goes off screen! There's a language suggestion for nested record assignment, which would allow you to do { document with Page.FooterSize.Height = 100. }
, but that's still a little ways off.
type Lens<'a,'b> = ('a -> 'b) * ('a -> 'b -> 'a)
Don't worry if this seems confusing! It will become much clearer in time.
Lenses are the functional equivalent to C#'s property set technology™ (basically page.FooterSize.Height = 100.
in C#).
They're quite simple, just a pair of getter, setter functions.
let getFooterSize (p: Page) = p.FooterSize
let setFooterSize (p: Page) size = { p with FooterSize = size }
let getPage (d: Document) = d.Page
let setPage (d: Document) page = { d with Page = page }
let getWH (s: Size) = s.Width, s.Height
let setWH (s: Size) (w, h) = Size(w, h)
Of course, we can get values by simply using the compose right operator:
let (w, h) = (getPage >> getFooterSize >> getWH) document
But setting doesn't compose. But with just three simple operators, we can have something very readable:
let get (getter, _) = getter
let set (_, setter) = setter
let (>=>) (get_ab, set_ab) (get_bc, set_bc) =
get_ab >> get_bc,
fun a c -> set_ab a (set_bc (get_ab a) c)
Since our lenses are just pair of getters and setters:
let pageL = getPage, setPage
let footerL = getFooterSize, setFooterSize
let sizeL = getWH, setWH
That's it. Those are our lenses. We can now compose these lenses with the fish operator (>=>
) we had defined.
let (footer_w, footer_h) = get (pageL >=> footerL >=> sizeL) document
let document' = set (pageL >=> footerL >=> sizeL) document (footer_w, 100.)
Of course, you can write a lens in a much shorter form:
let pageL : Lens<Document, Page> =
(fun d -> d.Page), (fun d p -> { d with Page = p })
Upvotes: 4