Basics 19 - Double-Lifted Data
Love and hate relationship with programming. Always learning programming and Jazz.
What is Double-Lifted Data
Consider this:
let opt: int option = Some 1
let res: Result<int option, exn> = Ok opt
optis anint option, which means value 1 is packed inoptioncontaineroptis then packed in aResultSimply put, 1 is double-lifted
While any combination of double-lifting is theoretically possible, some of the cases you will practically and frequently encounter are:
Result<'T option, 'TError>:Indicates the result of an operation that may fail
In
Okcase, you may or may not have dataIn
Errorcase, you have an exceptionExample: executing a DB query that gets one result/row/document. There are various outcomes:
DB operation failed,
ErrorcaseDB operation succeeded, found the record,
Okcase ->SomecaseDB operation succeeded, no record found,
Okcase ->Nonecase
Result<'T seq, 'TError> or Result<'T list, 'TError> or Result<'T array, 'TError>:Indicates the result of an operation that may fail
In
Okcase, you will get aseq/list/arrayof itemsIn
Errorcase, you have an exceptionExample: executing a DB query that gets multiple results/rows/documents. There are various outcomes:
DB operation failed,
ErrorcaseDB operation succeeded, found the records,
Okcase ->'T seq/list/array- In
Okcase, there may be 0 or more items in'T seq/list/array
- In
Problem with Double-Lifted Data
Consider this:
let res: Result<int option, exn> = Ok (Some 1)
let add1 num = num + 1
We have
add1function which can be used for mappingHow do we
useadd1 withres?Result.map add1 reswon't work. In this caseResult.mapexpects a function (mapper) that operates onint option, notint.There is no point in changing
add1;add1is our business logic, typically reusable
There are various ways to solve this issue.
Wrapper Function or Lamba Expression
The wrapper function add1Wrapper (or the lambda expression) takes int option as input:
let add1 num = num + 1
let add1Wrapper opt = Option.map add1 opt
Result.map add1Wrapper res
//// OR ////
Result.map (fun opt -> Option.map add1 opt) res
The problem with this approach is that the burden is now yours... for how many such functions can you create wrappers or lambda expressions.
Lifted Function
Remember map function can be used as a function lifter. Function liftedAdd1 is the lifted version of add1:
let add1 num = num + 1
let liftedAdd1 = Option.map add1
Result.map liftedAdd1 res
//// OR ////
let x = Result.map (Option.map add1) res
The problem with this approach, once again, is that the burden is yours... for how many such functions can you create lifted versions.
Let's look at another approach.
Functions that are aware of Double-Lifted Data
module Result =
let optMap mapper result = Result.map (Option.map mapper) result
let optBind binder result = Result.map (Option.bind binder) result
Here we have created two functions optMap and optBind, and put those in the Result module. This is done for consistency, you can call Result.optMap and Result.optBind.
let add1 num = num + 1
let add1ifEven num =
if num % 2 = 0 then Some(num + 1) else None
let res: Result<int option, exn> = Ok(Some 1)
Result.optMap add1 res |> printfn "%A" // Ok (Some 2)
Result.optBind add1ifEven res |> printfn "%A" // Ok None
Concluding Remarks
Dealing with double-lifted data isn't the most straight-forward
For rare cases, you may use lambda expressions or lifted functions
Best to create a library of functions that are aware of double-lifted data, as we will in coming articles
If you have reached so far, congratulations.
Keep reading!