I had a piece of code that solved really simple problem - it formated two dates to look better on a webpage. It looked like this:
function formatDateRange(
days: { firstDay: string, lastDay: string }
): string {
let first, last;
first = dayjs(days.firstDay); //dayjs parses string that formatted like this "YYYY-MM-DD"
last = dayjs(days.lastDay); //and returns a wrapper for Date object
if (first.isSame(last, "date")) {
first = first.format("DD MMMM, YYYY")
last = ""
} else if (first.isSame(last, "month")) {
first = first.format("DD") + " ... "
last = last.format("DD, MMMM YYYY")
} else if (first.isSame(last, "year")) {
first = first.format("DD MMM") + " ... "
last = last.format("DD MMM, YYYY")
} else {
first = first.format("DD MMM, YYYY") + " ... "
last = last.format("DD MMM, YYYY")
}
return first + last
}
Than I decided to refactor it, and I got this:
//adapter that allows to use objects and arrays in function-composition
//EDIT: was called "callable"
function pluckFrom<T extends {}>(obj: T) {
return (key: keyof T) => obj[key]
}
// a function for function-composition
// (same as compose from redux but order is reversed)
function pipe(...funcs: Function[]) {
return funcs.reduce((f1, f2) => (...args: any) => f2(f1(...args)))
}
//provides an interface to apply array of functions to array of data
function apply<A>(maps: Array<(a: A) => any>) {
return {to: (arr: A[]) => arr.map((item, index) => maps[index](item))}
}
//takes array of dates, wrapped in Dayjs object and returns
//true if they're all the same in terms of unit of time
function isSame([day1, ...rest]: Dayjs[]) {
return (unit: UnitType) => rest.every(day => day.isSame(day1, unit))
}
interface DateRange {
firstDay: string;
lastDay: string;
}
//EDIT: removed redundant "same_" prefix
const case_to_format_map = {
"date": ["DD MMMM, YYYY", " "],
"month": ["DD ...", "DD, MMMM YYYY"],
"year": ["DD MMM ... ", "DD MMM, YYYY"],
" ": ["DD MMM, YYYY ... ", "DD MMM, YYYY"]
}
type Case = keyof typeof case_to_format_map
//EDIT: slightly refactored
function formatDateRange({firstDay, lastDay}: DateRange): string {
const
getCase = (days: Dayjs[]) => (
(["date", "month", "year", " "] as UnitType[])
.filter(isSame(days))[0]
) as Case
,
makeFormatter = (f: string) => (d: Dayjs) => d.format(f)
,
getFormatters = pipe(
getCase,
pluckFrom(case_to_format_map),
formats => formats.map(makeFormatter)
)
,
days = [firstDay, lastDay].map(dayjs)
return apply(getFormatters(days)).to(days).join(" ")
}
It's more code, but I like it because now I have 4 additional utility functions, it's more readable for me. Other than that, I'm not sure if I made better or worse. And so I have these questions:
Is it readable to you?
Is it aligned with functional style and best practices?
I'm not sure if I will ever need to extend this code but how can I make it more extendable?
callable
,pipe
andapply
are candidates to be called in other tasks. However in fact it's more code than what you had previously. Appart from that the second code is quite cryptical so hm well, it's quite hard to tell. \$\endgroup\$