-
-
Notifications
You must be signed in to change notification settings - Fork 505
Option
Giulio Canti edited this page Sep 2, 2022
·
12 revisions
Problem
I want to map a string array to an integer array and compute the total sum. Mapping a string to an int implies that the string might be a number, but it might not as well. So, question is, how do I structure the program so I can handle the resulting exception when a string is actually not a number? How to handle exceptions properly in a functional style?
Solution
import * as assert from 'assert'
import { pipe } from 'fp-ts/function'
import * as M from 'fp-ts/Monoid'
import * as N from 'fp-ts/number'
import * as O from 'fp-ts/Option'
import * as RA from 'fp-ts/ReadonlyArray'
const parseInteger = (s: string): O.Option<number> => {
const n = parseInt(s, 10)
return isNaN(n) ? O.none : O.some(n)
}
const getSum: (numbers: ReadonlyArray<number>) => number = M.concatAll(
N.MonoidSum
)
const solution = (input: ReadonlyArray<string>): O.Option<number> => {
// const parsing: readonly O.Option<number>[]
const parsing = pipe(input, RA.map(parseInteger))
// const numbers: O.Option<readonly number[]>
const numbers = pipe(parsing, RA.sequence(O.Applicative))
// const sum: O.Option<number>
const sum = pipe(numbers, O.map(getSum))
return sum
}
assert.deepStrictEqual(solution(['1', '2', '3']), O.some(6))
assert.deepStrictEqual(solution(['1', 'a', '3']), O.none)
You can rewrite the solution to a single pipeline
const solution = (input: ReadonlyArray<string>): O.Option<number> => {
return pipe(
input,
RA.map(parseInteger),
RA.sequence(O.Applicative),
O.map(getSum)
)
}
Note that map + sequence = traverse
, you can refactor the pipeline to
const solution = (input: ReadonlyArray<string>): O.Option<number> => {
return pipe(input, RA.traverse(O.Applicative)(parseInteger), O.map(getSum))
}
Alternatively if you just want to skip bad inputs but keep adding good ones
const solution = (input: ReadonlyArray<string>): number => {
return pipe(input, RA.filterMap(parseInteger), getSum)
}
assert.deepStrictEqual(solution(['1', '2', '3']), 6)
assert.deepStrictEqual(solution(['1', 'a', '3']), 4)
Optionally since input
is repeated you can get rid of pipe
and use flow
import { flow } from 'fp-ts/function'
const solution: (input: ReadonlyArray<string>) => number = flow(
RA.filterMap(parseInteger),
getSum
)
APIs returning a sentinel value
import { pipe } from 'fp-ts/function'
const doSomethingWithIndex = (n: number): number => n * 2
pipe(
['a', 'b', 'c'].findIndex((s) => s.length > 2), // no error
doSomethingWithIndex,
console.log
) // => -2, because findIndex returns -1 :facepalm: the type checker can't help us here
import * as RA from 'fp-ts/ReadonlyArray'
pipe(
['a', 'b', 'c'],
RA.findIndex((s) => s.length > 2), // error
doSomethingWithIndex,
console.log
)
APIs returning undefined
import { pipe } from '../src/function'
pipe(
['a', undefined, 'c'].find((s) => s === undefined || s === 'c'),
console.log
) // undefined (<= what does it means? Did it found an element or not?)
import * as RA from 'fp-ts/ReadonlyArray'
pipe(
['a', undefined, 'c'],
RA.findFirst((s) => s === undefined || s === 'c'),
console.log
) // some(undefined) (<= found an element which is `undefined`)