# Lenses and lists

Jul 11, 2016 · 6 min read

## The Problem

`type Address{  name: string;  number: int;  street: string;  city: string;  state: string;  country: string;}type ContactDetails {  numbers: string list;  addresses: Address list;}type Person = {    name: string;    age: int;    contact: ContactDetails}`

## Expanding Lenses

Challenge: we need a new lens function that can work with elements of a list

`let inline (@=) lens v = lens.update (List.append [v])let inline (-=) lens v = lens.update (Array.filter (fun a -> v <> a))let updatedPerson = person |> (Contact >>| Addresses ) @= anAddress`
`let nth (n:int) : Lens<’a list,’a option> = {   get = fun theList ->     match n with     | x when x < 0               -> None     | x when x >= theList.Length -> None     | _                          -> Some( theList.[n] )   set = fun v ->       List.mapi (fun i elem -> if i = n then v.Value else elem) }`
`let lens = ( Contact >>| Addresses >>| nth 2 >>| City )let updated = myPerson = lens.set "Christcurch"`

## Optional composition

Challenge: we need another combinator that stops at a None, or continues down that chain with the “Some” value.

`let inline (>>||)   (l1: Lens<'a,'b option>)   (l2: Lens<'b,'c >)   : Lens<'a, 'c option> =   {     get = fun (a:'a) ->        let b = l1.get a        match b with        | None -> None        | Some bVal -> Some( l2.get bVal )...`
`...     set = fun (c:'c option) (a:'a) ->        let b = l1.get a        match b,c with        | Some bVal, Some cVal ->           let optionBridge =              fun (x: 'b option) -> l2.set cVal (x.Value) |> Some           l1.update optionBridge a        |_,_ -> a  }`
`let lens = ( Contact >>| Addresses >>| nth 2 >>|| City )let updated = myPerson = lens.set "Christcurch"`

## Collection Searching Lenses

Challenge: we want to access a collection item by name

`let inline where (pred: 'a -> bool ) : Lens< 'a list,'a option> =     {      get = fun theList -> theList |> List.tryFind (pred)      set = fun v ->          List.map (fun elem -> if pred elem then v.Value else elem)    }`
`let compoundLens =     Contact     >>|  Addresses     >>|  where (fun x -> x.name = "work")    >>|| city`

## Duck typed lenses

`let inline isNamed <^a when ^a:(member name:string)> (nm:string) a =       (^a:(member name: string) a) = nm`
`let inline byName<^a when ^a:(member name:string)> (nm:string)                :Lens<^a list,^a option> =  {   get = fun theList -> theList |> List.tryFind (isNamed nm)   set = fun v ->     List.map (fun elem -> if (isNamed nm) elem then v.Value else elem) }`
`let compoundLens = Contact >>| Addresses >>| byName "work" >>|| Citylet updated = myPerson |> compoundLens.set "Christchurch"`

Written by

## Peter Bayne

#### Principal Consulting Software Developer at Haumohio.com, based in beautiful Christchurch, NZ. Writing about devops, F#, and software dev in general.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just \$5/month. Upgrade