Created
January 30, 2022 12:34
-
-
Save kayac-chang/953b37deefa8893cca2868d3390b3851 to your computer and use it in GitHub Desktop.
[learn FP with Kirby using `fp-ts`] Either
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Chain with Either | |
* === | |
* chain help solve nested either structure | |
*/ | |
import { flow, pipe } from "fp-ts/lib/function"; | |
import { Password, validate } from "./_prepare"; | |
import { map, Either, chain, right } from "fp-ts/Either"; | |
/** | |
* Scenerio. | |
* If hash function may throw error | |
*/ | |
type HashFn = (value: string) => Either<Error, string>; | |
type HashExecute = (password: Password) => Either<Error, Password>; | |
function hash(algorithm: HashFn): HashExecute { | |
return (password) => | |
pipe( | |
algorithm(password.value), | |
map((value) => ({ | |
...password, | |
value, | |
isHashed: true, | |
})) | |
); | |
} | |
import { createHash } from "crypto"; | |
/** | |
* using chain to flatten nested result | |
*/ | |
const logic = flow( | |
Password, | |
validate({ minLength: 8, capitalLetterRequired: true }), | |
chain( | |
hash((value) => | |
right( | |
createHash("md5").update(value).digest("hex") | |
// | |
) | |
) | |
) | |
); | |
// test both case | |
console.log(pipe("pw123", logic)); | |
console.log(pipe("Password123", logic)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Either (Result) | |
* === | |
* An Either is a type | |
* that represents a synchronous operation | |
* that can succeed or failed. | |
*/ | |
describe(`The Either type is either 'Right' or 'Left'`, () => { | |
type Either<E, A> = Left<E> | Right<A>; | |
// Left represents failure | |
interface Left<E> { | |
readonly _tag: "Left"; | |
readonly left: E; | |
} | |
// Right represents success | |
interface Right<A> { | |
readonly _tag: "Right"; | |
readonly right: A; | |
} | |
}); | |
/** | |
* Why use Eithers ? | |
* | |
* Eithers are essential for capturing error states, | |
* we need either because we cannot break pipelines by throwing errors. | |
*/ | |
/** | |
* Eithers is always type-safe. | |
* With using try-catch, the error is always type unknown. | |
* With Eithers, we know every error state based on type signature. | |
*/ | |
/** | |
* Example | |
* Password Validate | |
* The password must be at least 8 characters long and have at least 1 capital letter. | |
* If the password is valid, we will hash it. | |
*/ | |
// 1. Create two error to for 'min length' and 'at least 1 capital' | |
function MinLengthValidationError(minLength: number) { | |
return new Error( | |
`password fails to meet min length requirement: ${minLength}` | |
); | |
} | |
function CapitalLetterMissingValidationError() { | |
return new Error(`password is missing a capital letter`); | |
} | |
// 2. Define Password type | |
interface Password { | |
_tag: "Password"; | |
value: string; | |
isHashed?: boolean; | |
isValidated?: boolean; | |
} | |
function Password(value: string): Password { | |
return { _tag: "Password", value }; | |
} | |
// 3. Validate the Password with Password Specification | |
import * as Either from "fp-ts/Either"; | |
type PasswordSpecification = { | |
minLength?: number; | |
capitalLetterRequired?: boolean; | |
}; | |
function validate(specification: PasswordSpecification) { | |
const { minLength = 0, capitalLetterRequired = false } = specification; | |
return (password: Password) => { | |
if (password.value.length < minLength) { | |
return Either.left(MinLengthValidationError(minLength)); | |
} | |
if (capitalLetterRequired && !/[A-Z]/.test(password.value)) { | |
return Either.left(CapitalLetterMissingValidationError()); | |
} | |
return Either.right({ ...password, isValidated: true }); | |
}; | |
} | |
// 4. Define Hash function | |
type HashFn = (value: string) => string; | |
type HashExecute = (password: Password) => Password; | |
function hash(algorithm: HashFn): HashExecute { | |
return (password) => ({ | |
...password, | |
value: algorithm(password.value), | |
isHashed: true, | |
}); | |
} | |
// 5. pipe | |
import { flow, pipe } from "fp-ts/function"; | |
import { createHash } from "crypto"; | |
const logic = flow( | |
Password, | |
validate({ minLength: 8, capitalLetterRequired: true }), | |
Either.map( | |
hash((value) => | |
// | |
createHash("md5").update(value).digest("hex") | |
) | |
) | |
); | |
// 6. Test two scenerio, one for failer, one for succeed | |
console.log(pipe("pw123", logic)); | |
console.log(pipe("Password123", logic)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment