Skip to content

Instantly share code, notes, and snippets.

@ducin
Last active October 30, 2021 11:58
Show Gist options
  • Save ducin/f6ceceea520fb57fb2bfd81bd12ae381 to your computer and use it in GitHub Desktop.
Save ducin/f6ceceea520fb57fb2bfd81bd12ae381 to your computer and use it in GitHub Desktop.
Functional Composition with Static Types in TypeScript, Tomasz Ducin, Wrocław TypeScript #3, 2019.03.27
/**
* FUNCTIONAL COMPOSITION
* with static typing in TypeScript
*
* Tomasz Ducin
* http://ducin.it
* twitter: @tomasz_ducin
*/
import { Developer, Nationality } from './types'
declare const devs: Developer[]
console.log(devs.length)
// 1. jumping over a fn signature (fn -> fn)
// 2. pipe/compose
type ConditionFn<T> = (t: T) => boolean
type DeveloperConditionFn = ConditionFn<Developer>
const earnsALot = (d: Developer) => d.salary > 8000
const earnsCrap: DeveloperConditionFn = (d) => d.salary < 2000
const knowsJavaScript: DeveloperConditionFn = (d: Developer) => d.skills.includes('JavaScript')
const hasNationality = (n: Nationality): DeveloperConditionFn =>
(d: Developer) =>
d.nationality === n
const all = <T>(...fns: ConditionFn<T>[]): ConditionFn<T> =>
(t: T) => !fns.find(cfn => !cfn(t))
const candidateCriteriaMet = all(
earnsALot,
knowsJavaScript,
hasNationality('PL')
)
// or
// atLeast
type ComparatorFn<T> = (e1: T, e2: T) => number
const earnsMoreThan: ComparatorFn<Developer> = (d1, d2) => d1.salary - d2.salary
const reverse = <T>(cfn: ComparatorFn<T>) =>
(e1: T, e2: T) => -cfn(e1, e2)
const earnsLessThan = reverse(earnsMoreThan)
const isYoungerThan: ComparatorFn<Developer> = (d1, d2) => d2.personalInfo.age - d1.personalInfo.age
const combinedSort = <T>(...cfns: ComparatorFn<T>[]): ComparatorFn<T> =>
(e1, e2) => {
const firstFn = cfns.find(cfn => !!cfn(e1, e2))
return firstFn ? firstFn(e1, e2) : 0
}
const theOrderOfGod = combinedSort(
isYoungerThan,
reverse(earnsMoreThan),
)
// console.log(devs.filter(candidateCriteriaMet).length )
console.log(devs.sort(theOrderOfGod)[0].personalInfo.age )
function pipe<T, U, V>(
f: (t: T) => U,
g: (u: U) => V
): (t: T) => V
function pipe<T, U, V, W>(
f: (t: T) => U,
g: (u: U) => V,
h: (v: V) => W,
): (t: T) => W
function pipe<T, U, V, W, X>(f: (t: T) => U, g: (u: U) => V, h: (v: V) => W, i: (w: W) => X): (t: T) => X
function pipe (...fns: Function[]) {
return (value) => fns.reduce((current, fn) => fn(current), value )
}
const filter = <T>(cfn: ConditionFn<T>) =>
(collection: T[]) =>
collection.filter(cfn)
const getEasterBonus = pipe(
filter(all(
earnsCrap,
hasNationality('PL')
)),
devs => devs.map(d => d.salary / 4),
amounts => amounts.reduce((sum, n) => sum + n)
)
console.log('bonus!', getEasterBonus(devs))
{
"compilerOptions": {
"lib": ["es2018"]
}
}
export type Nationality = "US" | "UK" | "FR" | "DE" | "NL" | "PL" | "IT" | "ES";
export type ContractType = "contract" | "permanent";
export type Developer = {
"id": number;
"nationality": Nationality,
"salary": number;
"office": [string, string];
"firstName": string;
"lastName": string;
"title": string;
"contractType": ContractType;
"email": string;
"hiredAt": string;
"expiresAt": string;
"personalInfo": {
"age": number;
"email": string;
"dateOfBirth": string;
},
"skills": string[];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment