const names = [
"alonzo church",
"Haskell curry",
"stephen_kleene",
"John von Neumann",
"stephen_kleene",
];
const result = []
for (let i = 0; i < names.length; i++) {
const n = names[i];
if (n !== undefined && n !== null) {
const ns = n.replace(/_/, " ").split(" ");
for (let j = 0; j < ns.length; j++) {
let p = ns[j];
p = p.charAt(0).toUpperCase() + p.slice(1);
ns[j] = p;
}
if (result.indexOf(ns.join(" ")) < 0) {
result.push(ns.join(" "));
}
}
}
console.log(result); // ['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']
const isValid = (el) => !_.isUndefined(el) && !_.isNull(el);
const replace_toBlank = (value) => value.replace(/_/, " ");
const splitWithBlank = (value) => value.split(" ");
const reduceCallback = (fullName, partOfName, idx, arr) => {
fullName +=
partOfName[0].toUpperCase() +
partOfName.substring(1) +
(idx === arr.length - 1 ? "" : " ");
return fullName;
};
const mapCallbackWithReduce = (data) => data.reduce(reduceCallback, "");
const resultNames = _.chain(names)
.filter(isValid)
.map(replace_toBlank)
.map(splitWithBlank)
.map(mapCallbackWithReduce)
.uniq()
.value();
console.log(resultNames); // ['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']
위의 코드는 names라는 배열 내의 이름 데이터들의 규격을 맨 앞문자를(각 이름의) 대문자로 바꾸면서 surname과 firstname 사이를 ' '으로 맞추는 로직을 나타낸다.
위에 2중 for문을 사용한 부분이 절차형 프로그래밍으로 만든거고, 아래가 선언형으로 만든거다.
근데 가만보면,, 선언형이 더 길다..?!
=> 맞다 선연형 프로그래밍 코드의 양이 더많다. 하지만, 여기서 포인트는 replace_toBlank
, splitWithBlank
이 함수를 나중에 재사용한다는 가정하에 만들었다고 생각해보면, 더 효율적일 수 있다는 점이다. 물론 재사용 안할 수 있고, 그에 따라 명령형이 더 낫다고도 할 수 있다. 하지만, 개인적으로 가독성 측면이나 후에 내가 이 코드를 다시 봤을 때 무슨 로직인가를 파악하기에 함수형 프로그래밍이 추상화가 잘돼 있어서 굳이 명령형처럼 안에 로직을 하나하나 다시 보지 않고서도 뭘하는 로직인지를 알 수 있을 것 같다.
for (const lang of Object.keys(addForm.answerData)) {
const typeLang = lang as LangType;
const mappedArr = [];
if (addForm.answerFormats[typeLang] === 'json') {
try {
if (addForm.answerJson[typeLang].value.length === 0) {
result[typeLang] = [];
} else {
result[typeLang] = JSON.parse(addForm.answerJson[typeLang].value);
}
} catch {
throw Error('422');
}
} else {
for (const el of addForm.answerData[typeLang]) {
if ((el.type === 'text' || el.type === 'html') && isValid(el.text)) {
mappedArr.push({
type: el.type,
text: el.text.trim(),
});
} else if (el.type === 'reply' && isValid(el?.replies)) {
mappedArr.push({
type: 'reply',
replies: el.replies.map((reply) => reply.trim()),
});
} else if (el.type === 'button' && isValid(el?.buttons)) {
mappedArr.push({
type: el.type,
buttons: [
{
title: returnStringOrEmptyString(el.buttons.title.value).trim(),
url: returnStringOrEmptyString(el.buttons.main.value).trim(),
action:
el.buttons.type[0] === 'custom'
? el.buttons.action.value.trim()
: faqJSON.addForm.actionButtonTypeMap[el.buttons.type[0]],
id: returnStringOrEmptyString(el?.buttons?.id?.value).trim(),
file_name: returnStringOrEmptyString(el.buttons.fileName).trim(),
},
],
});
} else if (el.type === 'image' && isValid(el?.images)) {
mappedArr.push({
type: el.type,
images: el.images.map((image) => image.src),
});
}
}
result[typeLang] = mappedArr;
}
}
위와 같은 코드를 아래와 같이 바꿨다. 완벽하게 선언형으로 컨버팅하진 못했지만, 그래도 훨씬 깔끔하고, 가독성 있는 코드로 바꿀 수 있었다.
Object.keys(addForm.answerData).forEach((langType) => {
const typeLang = langType as LangType;
if (addForm.answerFormats[typeLang] === 'json') {
try {
if (addForm.answerJson[typeLang].value.length === 0) {
result[typeLang] = [];
} else {
result[typeLang] = JSON.parse(addForm.answerJson[typeLang].value);
}
} catch {
throw Error('422');
}
} else {
result[typeLang] = _.chain(addForm.answerData[typeLang]).filter(filterAnswerDataIsValid).map(mapAnswerDataWithType).value();
}
});
앞으로 최대한 이렇게 선언형 프로그램이 방식으로 사고하는 버릇을 들이자.