: 위의 명제(?)는 아니지만, 문장을 위해서 튜플
이라는 자료 구조를 사용하고자 한다. 튜플은 형식이 다른 원소를 한 데 묶어서 다른 함수에 건네주는 일이 가능한 불변적인 자료구조
이다. 물론, 배열이나 객체를 이용할 수 있지만, 튜플을 사용하면 좀 더 쉽게 '불변성'을 유지할 수 있고, 이형배열(본래 배열이란 길이가 정해져있고, 자료 형식도 하나로 정해져있는 자료구조다)을 만들지 않아도 되며, 하나의 데이터로 묶기 위해 임의의 형식을 갖는 객체처럼 새로운 형식을 정의할 필요도 없다는 장점이 있다. 그래서 결론적으로 튜플로 함수의 항수를 줄일 수 있다.
const checkType = (type) => {
return (val) => {
if(typeof val !== type) {
throw new TypeError(`형식 불일치: ${type}이어야 하는데, ${typeof val} 입니다.`);
} else {
return val;
}
}
}
const Tuple = function() {
const typeInfo = Array.prototype.slice.call(arguments);
const _T = function () {
const values = Array.prototype.slice.call(arguments);
if(values.some(
val => val === null || val === undefined)) {
throw new ReferenceError("튜플은 null 값을 가질 수 없습니다!.");
}
if(values.length !== typeInfo.length) {
throw new TypeError("튜플 항수가 프로토타입과 맞지 않습니다!");
}
values.forEach((val, index) => {
this['_' + (index + 1)] = checkType(typeInfo[index])(val);
}, this);
Object.freeze(this);
}
_T.prototype.values = function () {
return Object.keys(this).map(k => this[k]);
}
return _T;
}
const normalize = str => str.replace(/\-/g, '');
const trim = str => str.replace(/^\s*|\s*$/g, '');
const Status = Tuple('boolean', 'string');
const isValid = function (str) {
if(str.length === 0) {
return new Status(false, '잘못된 입력입니다. 빈 값일 리 없지요!');
} else {
return new Status(true, '성공!');
}
}
isValid(normalize(trim('444-44-4444'))); // _T {_1: true, _2: '성공!'}
const StringPair = Tuple('string', 'string');
const name = new StringPair('Barkley', 'Rosser');
[first, last] = name.values();
first; // 'Barkley'
last; // 'Rosser'
: 위에서 만든 checkType 함수는 커링을 이용해서 만들었다. 하지만, RamdaJS를 써서 좀 더 편하게 만들어보자
const checkType = R.curry((type, val) => {
if (typeof val !== type) {
throw new TypeError(
`형식 불일치: ${type}이어야 하는데, ${typeof val} 입니다.`
);
} else {
return val;
}
});
const result = checkType("boolean")("str"); // TypeError
이런식으로 커링은 JS 특성상(not TS) 여러개의 인수를 받아야하는 함수에서 한개의 인수만을 입력해도 나머지 인수가 undefined로 되는 것을 방지하며, 동시에 위에서 언급한 간단한 함수의 조건인 단항 함수
요건도 충족해준다.
위에서 말한 튜플도 단항함수를 만들기 위한 좋은 방법이지만, 커링을 이용하면 위와 같이 여러개의 인수를 무조건 받도록 강제하는 동시에 단항 함수로서 함수를 쓸 수 있다.
: 객체 지향에서 인터페이스는 특정 클래스를 상속 받는 클래스에서 반드시 구현해야할 규약을 정해놓은 추상적 형식이다. 어떤 인터페이스에 findStudent라는 함수가 있으면 이 인터페이스를 구현한 코드는 반드시 이 함수를 구현해야 한다.
** 함수 팩토리 = 객체를 생성해서 반환하는 함수
이 때, 포인트는 호출자 관점에서 메서드를 호출한다는 사실이 중요하지 객체의 출처는 관심이 없다는 것이다. 즉, 특정 타입을 넣어서 특정 결과값만(참조 투명성 및 순수함수) 제대로 나오면 상관이 없다는 것.
예를 들어서, 어떤 학생 객체를 찾아서 리턴하는 함수인 findStudent
라는 함수가 있다고 해보자. 이 때, 학생 객체는 DB에서 찾을수도 있고, caching 된 메모리(배열이라고 하자)에서 찾을 수 있다고 해보자. 이 때, 호출자 관점에선 앞서 말한 것처럼 결과값으로 학생 객체가 정상적으로 나오면 그만이다. 이 때, 커링을 이용해서 이를 구현해보면(객체 지향에서 인터페이스가 아닌),
결론적으로
(function () {
localStorage.setItem("test", JSON.stringify(["yongki", "jihoon", "chaeun"]));
})();
const array = JSON.parse(localStorage.getItem("test"));
console.log(array);
const findStudentFromCache = R.curry((arr, ssn) => {
return Array.isArray(array) ? arr[ssn] : null;
});
const findStudentFromDB = R.curry((db, ssn) => {
return db[ssn];
});
const dummyDB = {
0: "yongki",
1: "jihoon",
2: "chaeun",
};
const findStudent = (type) =>
type === "db" ? findStudentFromDB(dummyDB) : findStudentFromCache(array);
const studentName = findStudent("cache")("2");
console.log(studentName); // chaeun
const studentName = findStudent("cache")("2");
console.log(studentName); // chaeun
이렇게 호출자 시점에서는 findStudent
를 호출할뿐 그 안에서 findStudentFromDB
를 쓰는지 findStudentFromCache
를 쓰는지 어떤 방식으로 실행되는지 알 필요가 없이 Student 값을 결과값으로 받을 수 있다.
: 사실 이렇게 재사용 가능한 함수 템플릿을 구현해 쓸지는 의문이지만, 무슨 말인지 이해하는 용도로 예시 코드를 짜봤다.
아래 코드는 이런 코드다.
randomSubjectNameForAUniv
이렇게 각각 함수를 만드는게 아니라 모듈성을 갖게 만들어서 재사용해서 쓰고 싶다고 생각해보자. 그 결과는 이러한 코드가 된다.
const getRandomSubjectName = R.curry(
(majors, majorCode, subjects, subMajorName) => {
const randomIndex = Math.floor(Math.random() * 3);
const subjectName = subjects[majors[majorCode]][subMajorName];
return subjectName ? subjectName[randomIndex] : "수강 신청 실패";
}
);
const XUnivMajors = [
"철학",
"심리학",
"사회학",
"컴퓨터 공학",
"경제학",
"경영학",
];
const XUnivSubjects = {
철학: {
미학: ["미학 이론", "서양 미학사", "마그리트 미학"],
논리학: ["논리학 개괄", "비트겐슈타인 철학"],
인식론: ["인지과학과 인식론", "칸트 인식론"],
},
"컴퓨터 공학": {
자료구조: ["자료구조 개론"],
네트워크: ["OSI 7계층", "네트워크 아키텍처 설계"],
프로그래밍: ["파이썬으로 배우는 크롤링", "자바 입문", "C언어"],
},
};
const XUnivSubjectsVersionForSecondSemester = {
철학: {
미학: ["메를로퐁티의 눈", "서양 미학사2", "헤겔 미학"],
논리학: ["비트겐슈타인 철학2", "수리 논리학"],
인식론: ["매트릭스와 인식론", "오컴의 면도날"],
},
"컴퓨터 공학": {
자료구조: ["자료구조 심화"],
네트워크: ["HTTP 이론", "네트워크 아키텍처 설계2"],
프로그래밍: [
"JS로 배우는 FE 프로그래밍",
"자바 입문2",
"C언어2",
"JAVA로 배우는 백엔드 프로그래밍",
],
},
};
const XUnivSystemGetRandomSubject = getRandomSubjectName(XUnivMajors);
const MAJOR_CODE = 0;
const MAJOR_CODE_2 = 3;
const XUnivRandomSubjectSystemForPhilosophyMajor =
XUnivSystemGetRandomSubject(MAJOR_CODE);
const XUnivRandomSubjectSystemForCSMajor =
XUnivSystemGetRandomSubject(MAJOR_CODE_2);
const studentYongKi = {
preference: "미학",
major: "philosophy",
};
const studentJihoon = {
preference: "프로그래밍",
major: "컴퓨터 공학",
};
const XUnivRandomSubjectSystemForPhilosophySubMajorsForFirstSemester =
XUnivRandomSubjectSystemForPhilosophyMajor(XUnivSubjects);
const XUnivRandomSubjectSystemForCSSubMajorsForSecondSemester =
XUnivRandomSubjectSystemForCSMajor(XUnivSubjectsVersionForSecondSemester);
const randomSubjectNameForThisSemester =
XUnivRandomSubjectSystemForPhilosophySubMajorsForFirstSemester(
studentYongKi.preference
);
const randomSubjectNameForNextSemester =
XUnivRandomSubjectSystemForCSSubMajorsForSecondSemester(
studentJihoon.preference
);
console.log(randomSubjectNameForThisSemester); // 서양 미학사
console.log("-----------------------------------");
console.log(randomSubjectNameForNextSemester); // 마그리트 미학
재사용을 억지로 한 느낌이 조금은 있지만, XUnivSystemGetRandomSubject
이걸 사용해서 두개의 전공에 맞는 XUnivRandomSubjectSystem
을 만들어냈다. 이 system을 가지고 first, second semester에 맞는 함수도 만들어냈다. 물론 전공을 똑같이 했으면 first, second semester 별로 나뉜 XUnivRandomSubjectSystemForSubMajors
의 재사용이 더 두드러졌겠지만, 일단 패스한다.
위와 같이 커링을 쓰면 재사용성이 향상되며, 앞서 말한 중요한 요소 중에 하나인(커링의 장점 중) 다인수 함수를 단항 함수로 바꿔준다.