3 ms·
Even in OO languages, I'm almost exclusively in an FP style because that style focuses on minimizing redundant code (e.g., don't write loops write loop transfor
by KirinDave 9y ago
Even in OO languages, I'm almost exclusively in an FP style because that style focuses on minimizing redundant code (e.g., don't write loops write loop transformer functions), minimizing redundant top level functions (segmentations, hand-tuned stateful getter/setters, etc), and maximizing the opportunity for correctness.
The last part is pretty important because properly testing software that talks to an external service is essentially impossible. Either you test in an integration setting where repeatable results are difficult to maintain, or you mock out your services and then you're testing your opinions against other opinions.
Here's an example of how I push this style to typescript, heavily redacted to remove company-specific marks, names and strutures: (this code is simple by intention; we may have a jr dev on the team soon and an excessive number of generator abstractions could help with this process but would cause readability issues).
function splitOrders(orders: e.OrderResult[]): {[key:string]: e.OrderResult[]} {
return orders.reduce(
(accum, borders) => {
// I sure would love a better idiom for non-destructive map updates but
// this isn't my primary language. Suggestions welcome!
let next = {...accum};
next[borders.datatype] = (next[borders.datatype] || []).concat([borders])
return next
}, {})
}
export async function undoifyUserProcess(env: any,
tolkien: client.tolkien,
token: client.APIToken,
spotData: IThingData): Promise<any> {
const legendaryCustomer = await e.getCustomerById(env, spotData.customer_id);
const orderResults = await e.findThingContractDetails(env, +spotData.spot_id);
const workSchedule = splitOrders(orderResults);
let btasks = scheduler.undoifyCustomerFromOrders(
env,
token,
{ key: legendaryCustomer.userKey },
workSchedule['newstyle'].map((wi => wi.bigId)));
let ltasks = (workSchedule['legacy'] || []).map(async (workItem) => {
// .. process elided but...
const nodeKey, groupKey = ["things", "were", "removed"];
const customerEmail = legendaryCustomer.some_field + "removed work"
// Factoring this out would have introduced a function with a bunch of parameters
// or state that is only synthesized here, so we left it embedded & closed over.
try {
return await omg.undoifyUserWithTagGroup(env, tolkien, customerEmail, nodeKey, groupKey);
} catch(e) {
console.log(`... malformed database call your mom for help ...`);
throw e;
}
})
try {
const work: Promise<any>[] = [btasks, ...ltasks];
const result = await Promise.all(work);
return await e.recordUndoifyingThing(env, +spotData.spot_id);
} catch(e) {
console.log(`...`);
}
}
If a language can't express a decently abstract functional style, I do my best to avoid it. Still, I've had to write a bit of Golang at my current job despite its utter lack of descriptive capabilities. It's poignantly frustrating to deal with, but a good reminder of how bad life used to be for programmers.
- scottmf 9y agoMaybe I'm not quite understanding the first function, but you could probably write it like this: (accum, borders) => ({ ...accum, [borders.datatype]: (accum[borders.datatype] || []).concat([borders]) }, {}) although it's a little codegolfy and I'd personally write it more like this: (accum, borders) => { const datatype = accum[borders.datatype] || []; datatype.push(borders); return { ...accum, [borders.datatype]: datatype, }; }, {})
- KirinDave 9y agoPush modifies the prior data structure. I could use it here but I do my best to avoid using push unless I'm very deliberately mutating an array iteratively. In this case it would work, but I don't like afforidng myself mutability as a short cut. Nice point on the first part though.
- bpicolo 9y agoLocal-scoped mutability is not typically an issue. It's mutability at higher levels that cause things to be tough to track down. Seems like that would be generally overkill to worry about, especially in languages where immutability isn't native
- KirinDave 9y ago> Local-scoped mutability is not typically an issue. But our refactoring tools really don't reveal the extend of variables, so while I may be confident in this outcome in the case where I write it, I leave a bit of a caveat that's difficult to document for subsequent developers. Same with my use of const that was noted above, it's more a message to future developers than a useful assertion (const does very, very little in js). If we were using Purescript or Haskell where I could assert this caveat with types, I'd do that instead. Even in Clojure, where it's actually kinda painful to leak the mutable contexts, I might do it. Too easy to lose control of stuff during a refactor in Typescript, so I don't.
- kevmo314 9y ago