Option

73 阅读2分钟

fromNullable

函数签名

export declare const fromNullable: <A>(a: A) => Option<NonNullable<A>>

如果传入的值是undefinednull,则返回O.none,否则返回O.some(data)

expect(O.fromNullable(undefined)).toStrictEqual(O.none);
expect(O.fromNullable(null)).toStrictEqual(O.none);
expect(O.fromNullable(1)).toStrictEqual(O.some(1));
expect(O.fromNullable("")).toStrictEqual(O.some(""));
expect(O.fromNullable("sss")).toStrictEqual(O.some("sss"));
expect(O.fromNullable([])).toStrictEqual(O.some([]));
expect(O.fromNullable({ name: "Tom", age: 12 })).toStrictEqual(O.some({ name: "Tom",age: 12 }));

map

函数签名

// f:(a: A -> b: B) -> Option<a: A> -> Option<b: B>
export declare const map: <A, B>(f: (a: A) => B) => (fa: Option<A>) => Option<B>
  • 接收一个判断函数, f:(A) => B
  • 返回一个函数 (fa: Option<A>) => Option<B>
  • faO.none,返回O.none
  • faO.some,将O.some内包装的value传递给f:(A) => B
  • 将得到的B使用O.Option包装为O.some返回
const isOdd = (n: number) => n % 2 !== 0;

const isOddOption = O.map(isOdd); // (fa: O.Option<number>) => O.Option<boolean>

const result1 = isOddOption(O.some(10)); // O.some(false)
expect(result).toStrictEqual(O.some(false));

const result2 = isOddOption(O.none); // O.none
expect(result2).toStrictEqual(O.none);

组合案例

单纯使用pipe时会遇到这样的问题

interface Foo{
    bar: string
}

const foo1 = {
    bar: 'hello'
} as Foo | undefined

pipe(foo1, (f) => f?.bar) // hello

const foo2 = undefined;
pipe(foo2, (f) => f?.bar) // undefined

这里的匿名函数的参数名称的命名,貌似用什么名称都不大正确,所以可以使用结构方式

interface Foo{
    bar: string
}

const foo1 = {
    bar: 'hello'
} as Foo | undefined

pipe(foo1, ({ bar }) => bar) // hello

const foo2 = undefined;
pipe(foo2, ({ bar }) => bar) // 这里会报错

为了既解决参数命名问题和报错问题,此时可以使用fromNullablemap来实现

interface Foo{
    bar: string
}

const foo1 = {
    bar: 'hello'
} as Foo | undefined

pipe(
    foo1,
    O.fromNullable, // { _tag: 'Some', value: { bar: 'hello' } }
    O.map(({ bar }) => bar) // { _tag: 'Some', value: 'hello' }
)

pipe(
    undefined,
    O.fromNullable, // { _tag: 'None' }
    O.map(({ bar }) => bar) // { _tag: 'None' }
)

flatten

函数签名

// 扁平化`Option`,将`Options<Option<A>>`转化为`Option<A>`
export declare const flatten: <A>(mma: Option<Option<A>>) => Option<A>

在遇到逐层嵌套的undefined的时候,就需要用到flatten

interface Fizz {
  buzz: string
}

interface Foo {
  bar?: Fizz
}

const foo = { bar: undefined } as Foo | undefined

pipe(foo, (f) => f?.bar?.buzz) // undefined

可以使用O.fromNullablefoobar都变成Option类型:

import * as O from "fp-ts/Option";

pipe(
    foo,
    O.fromNullable,
    O.map(({ bar }) =>
        pipe(
            bar,
            O.fromNullable,
            O.map(({ buzz }) => buzz),
        )
    ),
) // { _tag: 'Some', value: { _tag: 'None' } }

最后得到的是一个嵌套的Option,但是我们想要得到的是一个不嵌套的Option,此时就需要用到O.flatten来展开。

import * as O from "fp-ts/Option";

pipe(
    foo,
    O.fromNullable,
    O.map(({ bar }) =>
        pipe(
            bar,
            O.fromNullable,
            O.map(({ buzz }) => buzz),
        )
    ),
    O.flatten,
) // { _tag: "None" }

chain(Flatmap)

函数签名

export declare const chain: <A, B>(f: (a: A) => Option<B>) => (ma: Option<A>) => Option<B>

chain的作用就是将mapflatten这两步操作合并成一步。

import * as O from "fp-ts/Option";

pipe(
    foo,
    O.fromNullable,
    O.map(({ bar }) => bar),
    O.chain(
        flow(
            O.fromNullable,
            O.map(({ buzz }) => buzz),
        )
    )
) // { _tag: "None" }
// const addressOption = O.fromNullable(person.address); // Option<string>

// const addressLine2Option = O.map<Address, O.Option<string>>(
//         (address) => O.fromNullable(address.addressLine2) // Option<string>
//     )(addressOption); // Option<Option<string>>

// const addressLine2OptionForreal = O.flatten(addressLine2Option); // Option<string>

// 以上代码等同于
const addressOption = O.fromNullable(person.address);
const addressLine2Option = O.chain<Address, string>(
        (address) => O.fromNullable(address.addressLine2)
    )(addressOption);

最终代码

type Address = {
    addressLine1: string;
    addressLine2?: string;
};

type Person = {
    name: string;
    age: number;
    address?: Address;
};

const personA = {
    name: "A",
    age: 20,
    address: {
        addressLine1: "hehe",
        addressLine2: undefined,
    },
};

const getAddressLine2Final = flow(
    O.fromNullableK((person: Person) => person.address),
    O.chain((address) => O.fromNullable(address.addressLine2)),
    O.getOrElse(() => "none")
);

const result = pipe(personA, getAddressLine2Final);

console.log(result);