Lenses and lists

Peter Bayne
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" >>|| City
let updated = myPerson |> compoundLens.set "Christchurch"

Continuing on

Peter Bayne

Written by

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