Either

66 阅读2分钟

需求: 实现一个方法,给定某个公民,判断他是否是美国18岁以下的公民,符合条件,返回该公民。

一般的实现方式:

const checkEligibility = (person: Person) => {  
    // 检查年龄
    if (!person.age <= 18) {  
        return "The Person's age must be under 18";  
    };  
 
    // 检查国籍
    if (person.nationality !== 'American') {  
        return "The Person's must be a U.S. citizen";  
    };  
  
    return person;  
};

fromPredicate

函数签名

export declare const fromPredicate: {
    <A, B extends A, E>(refinement: Refinement<A, B>, onFalse: (a: A) => E): (a: A) => Either<E, B>
    <A, E>(predicate: Predicate<A>, onFalse: (a: A) => E): <B extends A>(b: B) => Either<E, B>
    <A, E>(predicate: Predicate<A>, onFalse: (a: A) => E): (a: A) => Either<E, A>
}

接受两个函数,第一个函数判断数据是否符合规则,第二个函数,当不符合规则后,要执行的函数,如果符合规则,则返回E.right(1),如果不符合规则,返回E.left(1)

const person1 = {
    name: "Tom",
    age: 10,
};

const person2 = {
    name: "Tom",
    age: 20,
};

const isAdult = E.fromPredicate<number, string>(
        (age) => age >= 18,
        () => "未成年"
    );

console.log(isAdult(person1.age)); // { _tag: 'Left', left: '未成年' }
console.log(isAdult(person2.age)); // { _tag: 'Right', right: 20 }

案例1


type Person = {
  name: string;
  age: number;
  nationality: string;
  gender?: string;
};

const checkEligibility = (person: Person) =>
  pipe(
    person,
    // check the age first by using fromPredicate
    E.fromPredicate(
      (person) => person.age <= 18,
      () => "The Person's age must be under 18"
    ),
    // check the nationality next
    E.chain(
      E.fromPredicate(
        (person) => person.nationality === "American",
        () => "The Person must be a U.S. citizen"
      )
    )
  );

const toUpperCasePerson = (person: Person) => ({
  ...person,
  name: person.name.toUpperCase(),
  nationality: person.nationality.toUpperCase(),
});

const femaleOnly = (person: Person) =>
  pipe(
    person,
    // check if gender is provided
    E.fromPredicate(
      (person) => !!person.gender,
      () => "Please provide your gender"
    ),
    // Check if gender is female
    // Since the data return from the above E.fromPredicate is an Either
    // We use chain (Flatten Map) to flatten Either<Either<string, Person>> into <Either<string, Person>
    E.chain((person) =>
      pipe(
        person,
        E.fromPredicate(
          (p) => p.gender === "female",
          () => "Female only"
        )
      )
    )
  );

// Same as above, but less codes
const femaleOnlyV2 = flow(
  E.fromPredicate(
    (person: Person) => !!person.gender,
    () => "Please provide your gender"
  ),
  E.chain(
    E.fromPredicate(
      (person) => person.gender === "female",
      () => "Female Only"
    )
  )
);

const personA = {
  name: "A",
  age: 18,
  gender: "female",
  nationality: "American",
} as Person;

const result = pipe(
  personA,
  checkEligibility,
  // Since toUpperCasePerson has the type signature Person -> Person,
  // We use map to unwrap the Person from Either, then return Person wrapped in Either
  E.map(toUpperCasePerson),
  // Since femaleOnly has the type signature Person -> Either<string, Person>
  // We use chain to unwrap the Person from Either, return Person wrapped in either and then flatten it
  E.chain(femaleOnlyV2),
  E.getOrElseW((e) => e)
);

console.log(result, "result");

案例2


type Nullable<T> = T | undefined | null;

const getValidStringEither = (field: Nullable<string>) =>
  pipe(
    field,
    // return Error if null or undefined
    E.fromNullable(new Error(`${field} is null or undefined`)),
    // return Error if the type is not string
    E.chain(
      E.fromPredicate(
        string.isString,
        (value) => new Error(`${value} is not a string`)
      )
    )
  );

// Curry the function so we can partial apply the regex
const getValidEmailFormat = (regex: RegExp) => (email: string) =>
  pipe(
    email,
    E.fromPredicate(
      (value) => regex.test(value),
      (value) => new Error(`"${value}" is not in a valid email format`)
    )
  );

// Partially apply the default validation regex
const getValidEmailUsingDefaultRegex = getValidEmailFormat(
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/
);

// Create an Either type and wrap the data inside Either because it can be an error or the data itself
const nameEither = getValidStringEither("myName");

/**
 * For email, first check if it is a valid string or not, then check if it is a valid email or not
 * We compose the functions here using 'flow'
 * And we use "chain" because we want to flatten the result
 */
const getValidEmailAddressesE = flow(
  getValidStringEither,
  E.chain(getValidEmailUsingDefaultRegex)
);

const emailEither = getValidEmailAddressesE("myemail@email.com");
//   console.log(emailEither);