Skip to content

Commit

Permalink
Added getOrElse
Browse files Browse the repository at this point in the history
  • Loading branch information
mantoci committed Jun 17, 2022
1 parent cde865f commit 246df49
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 78 deletions.
13 changes: 8 additions & 5 deletions examples/game-of-life/game-of-life.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { repeat } from '../../src/lambda/lambda'

type Cell = Either<string, string>

const createAliveCell = (): Cell => right('*')
const ALIVE_VALUE = '*'
const createAliveCell = (): Cell => right(ALIVE_VALUE)

const createDeadCell = (): Cell => left('.')
const DEAD_VALUE = '.'
const createDeadCell = (): Cell => left(DEAD_VALUE)

type CheckPatterns = [row: number, col: number][]

Expand Down Expand Up @@ -45,7 +47,7 @@ const createInitialState = (width: number, height: number): GameOfLifeState => (
export const createGameOfLife = (width: number, height: number): GameOfLife => {
const state: GameOfLifeState = createInitialState(width, height)

const getCell = (row: number, col: number) => maybe(state.grid[row]).mapRight((it) => maybe(it[col]))
const getCell = (row: number, col: number) => maybe(state.grid[row]).flatMap((it) => maybe(it[col]))

const setLivingCell = (row: number, col: number): void => {
maybe(state.grid[row]).fold(
Expand Down Expand Up @@ -89,15 +91,16 @@ export const createGameOfLife = (width: number, height: number): GameOfLife => {

if (livingNeighbours === 3) return createAliveCell()

return cell.mapRight((it) => (livingNeighbours < 2 || livingNeighbours > 3 ? createDeadCell() : right(it)))
return cell.flatMap((it) => (livingNeighbours < 2 || livingNeighbours > 3 ? createDeadCell() : right(it)))
})
)

const computeNextGeneration = (): void => {
state.grid = createNextGeneration()
}

const dumpGrid = (): string => state.grid.map((row) => row.map((cell) => cell.fold()).join('')).join('\n')
const dumpGrid = (): string =>
state.grid.map((row) => row.map((cell) => cell.getOrElse(() => DEAD_VALUE)).join('')).join('\n')

return {
countLivingNeighbours,
Expand Down
15 changes: 9 additions & 6 deletions examples/tennis-game/tennis-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ const MAX_SCORE_INDEX = SCORES.length - 1

const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)

const getAllScoreText = (player: Player) => maybe(SCORES[player.scoreIndex]).mapRight(capitalize).fold()
const getAllScoreText = (player: Player) =>
maybe(SCORES[player.scoreIndex])
.flatMap(capitalize)
.getOrElse(() => '')

const createPlayer = (name: string): Player => ({
name,
Expand Down Expand Up @@ -53,24 +56,24 @@ export const createGame = (firstPlayerName: string, secondPlayerName: string): T

const handleDraw = (): MaybeScore =>
isDeuce()
.mapRight(() => 'Deuce')
.mapLeft(() => isAll().mapRight(() => `${getAllScoreText(playerOne)} all`))
.flatMap(() => 'Deuce')
.mapLeft(() => isAll().flatMap(() => `${getAllScoreText(playerOne)} all`))

const handleAdvantage = (): MaybeScore => {
const rank = getRank()
const spread = Math.abs(rank)
const topPlayer = rank > 0 ? playerOne : playerTwo
return allCanWin()
.mapRight(() => (spread === 1 ? `Advantage ${topPlayer.name}` : nothing()))
.flatMap(() => (spread === 1 ? `Advantage ${topPlayer.name}` : nothing()))
.mapLeft(() => (canWin(topPlayer) && spread >= 2 ? just(`${topPlayer.name} wins`) : nothing()))
}

return {
getScore: (): string =>
isDraw()
.mapRight(handleDraw)
.flatMap(handleDraw)
.mapLeft(handleAdvantage)
.fold(() => `${getAllScoreText(playerOne)},${getAllScoreText(playerTwo)}`),
.getOrElse(() => `${getAllScoreText(playerOne)},${getAllScoreText(playerTwo)}`),
playerOneScores: () => score(playerOne),
playerTwoScores: () => score(playerTwo),
}
Expand Down
74 changes: 31 additions & 43 deletions src/either/either.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ const runMonadChecks = (adt: Either<Error, number>, of: (value: any) => Either<E
})

it('has left identity', () => {
const f = (a: any) => of(a).mapRight((x: number) => of(x * 2))
const f = (a: any) => of(a).flatMap((x: number) => of(x * 2))

expect(adt.mapRight(f).equals(f(adt))).toBe(true)
expect(adt.flatMap(f).equals(f(adt))).toBe(true)
})

it('has right identity', () => {
expect(adt.mapRight(of).equals(adt)).toBe(true)
expect(adt.flatMap(of).equals(adt)).toBe(true)
})

it('has associativity', () => {
const f = (a: any) => of(a).mapRight((x: number) => of(x * 2))
const g = (a: any) => of(a).mapRight((x: number) => of(x + 2))
const f = (a: any) => of(a).flatMap((x: number) => of(x * 2))
const g = (a: any) => of(a).flatMap((x: number) => of(x + 2))

adt
.mapRight(f)
.mapRight(f)
.equals(adt.mapRight((x: any) => f(x).mapRight(g)))
.flatMap(f)
.flatMap(f)
.equals(adt.flatMap((x: any) => f(x).flatMap(g)))
})
}

Expand Down Expand Up @@ -59,23 +59,23 @@ describe('Either', () => {
it('supports right return', () => {
const newAdt = right<Error, number>(999)

const actual = adt.mapRight(() => newAdt)

expect(actual).toBeRight(newAdt)
expect(adt.flatMap(() => newAdt)).toBeRight(newAdt)
expect(adt.flatMap(() => right(newAdt))).toBeRight(right(newAdt))
expect(adt.flatMap(() => right(newAdt))).not.toBeRight(newAdt)
})

it('supports left return', () => {
const newAdt = left<Error, number>(new Error())

const actual = adt.mapRight(() => newAdt)
const actual = adt.flatMap(() => newAdt)

expect(actual).toBeLeft(newAdt)
})

it('supports nested return', () => {
const newAdt = right<Error, number>(999)

const actual = adt.mapRight(() => right(newAdt))
const actual = adt.flatMap(() => right(newAdt))

expect(actual).toBeRight(right(newAdt))
})
Expand Down Expand Up @@ -108,20 +108,6 @@ describe('Either', () => {
expect(spy).toHaveBeenCalledWith(value)
})

describe('without callbacks', () => {
it('folds', () => {
expect(right(5).fold()).toBe(5)
})
})

describe('with left callback', () => {
it('folds', () => {
const actual = adt.fold(() => 999)

expect(actual).toBe(value)
})
})

describe('with both callbacks', () => {
it('folds', () => {
const actual = adt.fold(
Expand All @@ -134,6 +120,14 @@ describe('Either', () => {
})
})

describe('getOrElse', () => {
it('runs the proper callback', () => {
const actual = adt.getOrElse(() => 999)

expect(actual).toBe(value)
})
})

describe('when serialized', () => {
it('serializes to string', () => {
expect(adt.toString()).toBe('Right(2)')
Expand Down Expand Up @@ -172,15 +166,15 @@ describe('Either', () => {
it('supports right return', () => {
const newAdt = right<Error, number>(999)

const actual = adt.mapRight(() => newAdt)
const actual = adt.flatMap(() => newAdt)

expect(actual).toBeLeft(adt)
})

it('supports left return', () => {
const newAdt = left<Error, number>(new Error())

const actual = adt.mapRight(() => newAdt)
const actual = adt.flatMap(() => newAdt)

expect(actual).toBeLeft(adt)
})
Expand Down Expand Up @@ -221,20 +215,6 @@ describe('Either', () => {
expect(spy).toHaveBeenCalledWith(error)
})

describe('without callbacks', () => {
it('folds', () => {
expect(left(3).fold()).toBe(3)
})
})

describe('with left callback', () => {
it('folds', () => {
const actual = adt.fold(() => 999)

expect(actual).toBe(999)
})
})

describe('with both callbacks', () => {
it('folds', () => {
const actual = adt.fold(
Expand All @@ -247,6 +227,14 @@ describe('Either', () => {
})
})

describe('getOrElse', () => {
it('runs the proper callback', () => {
const actual = adt.getOrElse(() => 999)

expect(actual).toBe(999)
})
})

describe('when serialized', () => {
it('serializes to string', () => {
expect(adt.toString()).toBe('Left(Error("error"))')
Expand Down
18 changes: 10 additions & 8 deletions src/either/either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ const createRight = <E, A>(data: A) =>
() => false,
(it) => equal(it, data)
),
fold: <B>(_?: any, ifRight?: (data: A) => B) => (ifRight ? ifRight(data) : data),
mapRight: (ifRight) => rightOf<E, any>(ifRight(data)),
// ap: (fn) => fn(rightOf(data)),
fold: <B>(_: (left: E) => B, ifRight: (right: A) => B) => ifRight(data),
getOrElse: () => data,
flatMap: (ifRight) => rightOf<E, any>(ifRight(data)),
mapLeft: () => rightOf(data),
})

const createLeft = <E, A>(data: E) =>
seal<Left<E, A>>({
const createLeft = <E, A>(data: E) => {
const fold = <B>(ifLeft: (value: E) => B) => ifLeft(data)
return seal<Left<E, A>>({
...createSerializable('Left', data),
isLeft: true,
isRight: false,
Expand All @@ -40,11 +41,12 @@ const createLeft = <E, A>(data: E) =>
(it) => equal(it, data),
() => false
),
fold: <B>(ifLeft?: (value: E) => B) => (ifLeft ? ifLeft(data) : data),
mapRight: () => leftOf(data),
// ap: (fn) => fn(leftOf(data)),
fold,
getOrElse: fold,
flatMap: () => leftOf(data),
mapLeft: (ifLeft) => leftOf<any, A>(ifLeft(data)),
})
}

export const rightOf = <E, A>(data: A | Either<E, A>): Either<E, A> => (isEither(data) ? data : createRight(data))

Expand Down
10 changes: 5 additions & 5 deletions src/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { maybe } from './maybe/maybe'
describe('Integration', () => {
it('can be chained', () => {
const actual: string = right<string, number>(0)
.mapRight((it) => it + 10)
.mapRight((it) => it * 2)
.mapRight((it) => (it === 20 ? left('yessss') : it))
.flatMap((it) => it + 10)
.flatMap((it) => it * 2)
.flatMap((it) => (it === 20 ? left('yessss') : it))
.mapLeft(() => right(100))
.mapRight((it) => (it === 100 ? 'happy' : 'very sad'))
.flatMap((it) => (it === 100 ? 'happy' : 'very sad'))
.fold(
() => 'very sad',
(it) => it
Expand All @@ -21,6 +21,6 @@ describe('Integration', () => {
const actual = maybe(right<string, number>(0))

expect(actual.equals(actual)).toBe(true)
expect(actual.fold()).toBeRight(0)
expect(actual.getOrElse(() => right(NaN))).toBeRight(0)
})
})
10 changes: 4 additions & 6 deletions src/jest-matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,23 @@ const getValueCheckResults = (fnName: string, values: TestValues, pass: boolean)
},
})

const isRight = (values: TestValues) => values.received.equals(values.expected)

const isLeft = (values: TestValues) => values.received.equals(values.expected)
const equals = (values: TestValues) => values.received.equals(values.expected)

const toBeRight = <T>(received: AnyEither, expected: T) => {
const values: TestValues = { expected: rightOf(expected), received: rightOf(received) }
const pass = isRight(values)
const pass = equals(values)
return getValueCheckResults('toBeRight', values, pass)
}

const toBeLeft = <T>(received: AnyEither, expected: T) => {
const values: TestValues = { expected: leftOf(expected), received: leftOf(received) }
const pass = isLeft(values)
const pass = equals(values)
return getValueCheckResults('toBeLeft', values, pass)
}

const toBeNothing = (received: AnyEither) => {
const values: TestValues = { expected: nothing(), received: leftOf(received) }
const pass = isLeft(values)
const pass = equals(values)
return getValueCheckResults('toBeNothing', values, pass)
}

Expand Down
8 changes: 3 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@ interface EQ<E, A> {
}

interface Chainable<E, A> {
mapRight<B = A>(ifRight: (right: A) => A | B | Either<E, A> | Either<E, B>): Either<E, B>
flatMap<B = A>(ifRight: (right: A) => A | B | Either<E, A> | Either<E, B>): Either<E, B>

mapLeft<G = E>(ifLeft: (left: E) => E | G | Either<G, A> | Either<E, A>): Either<G, A>

// ap<B = A>(fn: (either: Either<E, A>) => FlatMapArgs<E, A, B>): Either<E, B>
}

interface Foldable<E, A> {
fold<B>(ifLeft: (left: E) => B, ifRight: (right: A) => B): B
fold(ifLeft: (left: E) => A): A
fold(): E | A

getOrElse(ifLeft: (left: E) => A): A
}

interface EitherPrototype<E, A> extends Serializable, EQ<E, A>, Chainable<E, A>, Foldable<E, A> {}
Expand Down

0 comments on commit 246df49

Please sign in to comment.