0

If I have two Option monads containing numbers, how can I add them together whilst not exiting the monad?

import { fromNullable, pipe, chain, map } from 'fp-ts/lib/Option'
let c1 = fromNullable(10)
let c2 = fromNullable(20)

// This is where I'm stuck. I would expect c3 to be an Option<number> containing 30. 
let c3 = pipe(c1, chain(map((x) => x + c2))

Thank you :-)

Roman Mahotskyi
  • 4,576
  • 5
  • 35
  • 68
Damien Sawyer
  • 5,323
  • 3
  • 44
  • 56

3 Answers3

2

You should use a sequence:

const c1 = Option.some(10);
const c2 = Option.some(20);

assertEquals(Array.sequence(Option.option)([c1, c2]), Option.some([10, 20]));

Look at https://fsharpforfunandprofit.com/posts/elevated-world-4/#sequence for explainations.

  • Hi. Thanks for that! It's not quite what I was after though. That appears to be combing them into an array inside an option, where I was looking to add them. I didn't know about sequences though, and you sent me down the rabbit hole with that link which was great. – Damien Sawyer Sep 14 '20 at 13:31
1

You are missing some pipe indirection in the snippet above. This will work:

import { option } from "fp-ts";
import { pipe } from "fp-ts/function";

declare const c1: option.Option<number>;
declare const c2: option.Option<number>;

const c3 = pipe(
  c1,
  option.chain(c1 =>
    pipe(
      c2,
      option.map(c2 => c1 + c2)
    )
  )
);

There are various alternatives depending on the context/usage. Here are a few

Using Do from fp-ts-contrib:

import { Do } from "fp-ts-contrib/lib/Do";

const c3b = Do(option.option)
  .bind("c1", c1)
  .bind("c2", c2)
  .return(({ c1, c2 }) => c1 + c2);

Using sequenceS from the Apply module:

import { sequenceS } from "fp-ts/Apply";

const c3c = pipe(
  sequenceS(option.option)({ c1, c2 }),
  option.map(({ c1, c2 }) => c1 + c2)
);
Roman Mahotskyi
  • 4,576
  • 5
  • 35
  • 68
Giovanni Gonzaga
  • 1,185
  • 9
  • 8
  • Thanks for that. They're great. One thing I just reaslised, which I didn't specify in the question, is that I want to be able to handle either of the variables being none. I would want the whole result to then be none. – Damien Sawyer Sep 14 '20 at 14:01
  • 1
    Yes, if one or both of c1, c2 are `None`, the whole computation will result in a `None` as well (and this is true for all the examples I provided) – Giovanni Gonzaga Sep 14 '20 at 18:22
  • aah... so they do. I just tried them all again and they were fine. I must have been tired when i tried last night. Thanks again for the detailed answer. You've given me a lot to sink my teeth into there. – Damien Sawyer Sep 15 '20 at 09:42
0

I came up with the following.

import {pipe} from 'fp-ts/lib/function'
import { getFoldableComposition } from 'fp-ts/Foldable'
import { array } from 'fp-ts/Array'
import { option,fromNullable } from 'fp-ts/Option'
import { monoidSum } from 'fp-ts/Monoid'

const F = getFoldableComposition(array, option)

let c1 = fromNullable(10) 
let c2 = fromNullable(20)
let c3 = fromNullable(undefined)
let c4 = fromNullable(undefined)

// returns 30
F.reduce([c1,c2],0, monoidSum.concat)  //?
// alternatively, also returns 30
F.reduce([c1,c2],0, (i,j)=>i+j)  //?

// The above unwarp the result though. To return as the monad need to lift the result

// returns some(30)
pipe(F.reduce([c1,c2],0, (i,j)=>i+j), fromNullable)  //?

// returns some(0)
pipe(F.reduce([c3,c4],0, (i,j)=>i+j), fromNullable)  //?
Roman Mahotskyi
  • 4,576
  • 5
  • 35
  • 68
Damien Sawyer
  • 5,323
  • 3
  • 44
  • 56