함수형 프로그래밍 #02 | 함수와 타입
2022년 09월 01일 13:03
📌 전역변수 사용 시 문제점(명령형 코드)
아래의 앱은 hot module replacement(HMR) 라는 module을 통해 변경사항이 발생한 파일에 대해서만 다시 실행이 되도록 한다.
//price.js
export const totalPrice = 0;
function addOrangePrice() {
for(let i = 0; i < 100; i++) {
totalPrice += 200;
}
}
export function main() {
addOrangePrice();
}//index.js
import * as C from "./price.js";
C.main();
const app = document.getElementById("app");
if (app !== null) {
app.innerHTML = `
<h1>total price: ${Math.rount(C.totalPrice)}</h1>
`;
}처음 앱을 실행했을 때에는 total price: 20000 이 화면에 출력되지만
index.js의 innerHTML 부분을 Total price로 변경해보자. 그럼 price.js 파일이 다시 실행되는게 아닌 main함수만 다시 실행이 되기 때문에 40000이 화면에 출력이 된다.
📌 위의 코드 순수함수로 바꾸기
//price.js
function priceOfOrange() {
return 200
}
export function getTotalPrice() {
return priceOfOrange() * 100;
}//index.js
import * as C from "./price.js";
const app = document.getElementById("app");
if (app !== null) {
app.innerHTML = `
<h1>total price: ${Math.rount(C.getTotalPrice())}</h1>
`;
}“과일 가격을 계산을 계산하기 위해 totalPrice에 과일 가격을 더해야 한다.” 라는 HOW의 관점에서 “과일 가격이 얼마이다.”라는 WHAT으로 코드를 작성한다.
📌 레코드 vs 순수함수
레코드와 순수함수는 같은 입력에 대해 같은 출력을 한다는 점에서 비슷해 보인다.
function getPrice(name: string) {
if (name === "tomato") {
return 7000;
} else if (name === "orange") {
return 15000;
} else if (name === "apple") {
return 10000;
}
}
const priceOfFruit = {
tomato: 7000,
orange: 15000,
apple: 10000
}위를 보면 같은 역할을 하지만 아래를 보자.
const isEven = {
tomato: true,
orange: true,
apple: false
}
//문자열의 길이가 홀수, 짝수인지 전부 하드코딩해야 하고 실수가 생긴다.
//데이터가 생길 때마다 추가해야한다.
const isEvenFn = (str: string) => str.length % 2 === 0
// 순수함수로 바꾼 결과이다.📌 수학에서 보는 함수합성
수학에서의 함수는 어떤 집합의 각 원소를 다른 집합의 유일한 원소에 대응시키는 이항 관계다. 함수합성은 한 함수의 공역이, 다른 함수의 정의역과 일치하는 경우 두 함수를 이어 하나의 함수로 만드는 연산이다.
추가적으로 수학에서의 집합은 특정한 조건에 맞는 별개의 원소들의 모임으로, 즉 수학에서의 타입은 할당할 수 있는 값들의 집합이다.
📌 전(total) 함수와 부분(partial) 함수
전 함수는 정의역의 모든 원소에 대해 반환값이 정의되어야 하는 함수를 말하고,
부분 함수는 가능한 입력 중에서 일부에만 반환값이 정의되어 있는 함수를 말한다.
=> getPrice는 부분 함수이다.
부분 함수는 실제 프로그래밍에서 구현할 수 없다. getPrice의 경우에는 undefined라는 리터럴 값을 추가하여 전함수로 만들어진다.
애초에 정의역에 해당하는 공역이 있는 타입으로 받으면 되지 않나? => 짝수만 허용하는 타입을 만들 수 있겠어?
//06
📌 함수 합성 시 undefined
function getPrice(name: string): number | undefined {
if (name === "tomato") {
return 7000;
} else if (name === "orange") {
return 15000;
} else if (name === "apple") {
return 10000;
}
}
function isExpensive(price: number) {
return price > 10000;
}위의 함수는 isExpensive(getPrice) 이렇게 합성할 수 없다. 그 이유는 getPrice의 return(공역)타입과 isExpensive의 정의역 타입이 일치하지 않기 때문이다.
function isExpensive(price: number | undefined) {
if (price === undefined) false;
return price > 10000;
}가장 간단한 해결방법으로 정의역 타입을 일치시켜 준다는 것이다. 그리고 해당 정의역에 대한 코드도 작성하는 것을 잊지말자.
📌 제네릭을 통한 함수합성
const compose = (isExpensive, getPrice) => (name) => {
return isExpensive(getPrice(name));
}
//함수형에서는 f,g,h 이렇게 함수를 표현한다.
const compose = (g, f) => (x) => {
return g(f(x));
}위의 함수에 타입을 표현해보자.
const compose = <A, B, C>(g: (y: B) => C, f: (x: A) => B) => (X: A) => {
return g(f(x));
}위에는 복잡한데 간단하게 매개변수를 지우고 보면 더 보기 편할 것이다.
<A, B, C>((B) => C, (A) => B) => A => C[B타입을 받아 C타입을 리턴하는 함수, A타입을 받아 B타입을 리턴하는 함수] 둘을 합성해서 A타입을 받아 C타입을 리턴하는 함수로 만들어주는 함수라는 것을 알 수 있다.
이렇게 보면 제네릭을 “타입을 생성하는 함수”로 볼 수 있다.