3장: 순수 함수로 순수한 행복을

아, 다시 순수해지는 것

우리가 분명히 해야 할 한 가지는 순수 함수의 개념입니다.

순수 함수란 동일한 입력을 받으면 항상 동일한 출력을 반환하고, 관찰 가능한 부작용이 없는 함수입니다.

slice와 splice를 예로 들어보겠습니다. 이 두 함수는 본질적으로 같은 작업을 하지만, 매우 다른 방식으로 수행합니다. slice는 매번 동일한 입력에 대해 동일한 출력을 반환하기 때문에 순수합니다. 반면 splice는 배열을 변경하여 항상 변화를 일으키므로 관찰 가능한 효과가 있습니다.

var xs = [1, 2, 3, 4, 5];

// 순수
xs.slice(0, 3);
//=> [1, 2, 3]

xs.slice(0, 3);
//=> [1, 2, 3]

xs.slice(0, 3);
//=> [1, 2, 3]

// 비순수
xs.splice(0, 3);
//=> [1, 2, 3]

xs.splice(0, 3);
//=> [4, 5]

xs.splice(0, 3);
//=> []
 

함수형 프로그래밍에서는 splice와 같은 데이터 변형 함수는 선호하지 않습니다. 우리는 항상 동일한 결과를 반환하는 신뢰할 수 있는 함수를 추구하기 때문에 splice와 같은 함수는 적합하지 않습니다.

다른 예를 보겠습니다.

// 비순수
var minimum = 21;

var checkAge = function(age) {
  return age >= minimum;
};

// 순수
var checkAge = function(age) {
  var minimum = 21;
  return age >= minimum;
};
 

비순수 코드에서는 checkAge가 결과를 결정하기 위해 변경 가능한 변수 minimum에 의존합니다. 즉, 시스템 상태에 의존하므로 외부 환경을 도입하여 인지 부하를 증가시킵니다.

이 예제에서는 큰 문제가 아닌 것처럼 보이지만, 상태 의존성은 시스템 복잡성을 증가시키는 주요 원인 중 하나입니다 . 이 checkAge 함수는 입력 외부의 요인에 따라 다른 결과를 반환할 수 있으며, 이는 순수하지 않을 뿐만 아니라 소프트웨어를 이해하는 데 부담을 줍니다.

순수한 형태에서는 완전히 독립적입니다. minimum을 변경할 수 없도록 만들어 순수성을 유지할 수도 있습니다. 이를 위해 객체를 생성하고 고정해야 합니다.

var immutableState = Object.freeze({
  minimum: 21,
});

SideEffect...

"SideEffect"을 더 살펴보고 직관을 개선해 봅시다. 순수 함수 정의에서 언급된 분명히 불길한 부작용이란 무엇일까요? 우리는 효과를 결과 계산 외에 발생하는 모든 것으로 정의할 것입니다.

효과 자체는 본질적으로 나쁘지 않으며, 다음 장에서 곳곳에서 사용할 것입니다. 문제는 SideEffect가 부정적인 의미를 가진다는 점입니다. 물은 자체로는 애벌레를 키우는 것이 아니지만, 정체된 물이 그렇듯이, 부작용도 프로그램에서 비슷한 문제를 일으킵니다.

SideEffect란 결과를 계산하는 동안 발생하는 시스템 상태의 변화 또는 외부 세계와의 관찰 가능한 상호작용입니다.

SideEffect의 예는 다음과 같습니다.

  • 파일 시스템 변경
  • 데이터베이스에 레코드 삽입
  • HTTP 호출
  • 데이터 변형
  • 화면 출력 / 로깅
  • 사용자 입력 받기
  • DOM 쿼리
  • 시스템 상태 접근

이 목록은 계속 이어집니다. 함수 외부와의 모든 상호작용은 부작용입니다. 이를 통해 부작용 없이 프로그래밍하는 것이 실용적이지 않을 수 있다고 의심할 수 있습니다. 함수형 프로그래밍 철학은 부작용이 잘못된 동작의 주요 원인이라고 가정합니다.

부작용을 사용하지 말라는 것이 아니라, 이를 제어된 방식으로 실행하고자 합니다. 나중 장에서 펑터와 모나드를 통해 이를 배우겠지만, 지금은 이러한 부정적인 함수를 순수한 함수와 분리해 보겠습니다.

부작용은 함수를 순수하지 않게 합니다. 이는 순수 함수가 동일한 입력에 대해 항상 동일한 출력을 반환해야 한다는 정의와 일치하지 않기 때문입니다.

왜 동일한 입력에 대해 동일한 출력을 고집하는지 자세히 살펴보겠습니다. 중학교 수학 수준으로 돌아가 봅시다.

중학교 수학

mathisfun.com에 따르면:

함수란 값들 사이의 특별한 관계입니다: 각 입력값은 정확히 하나의 출력값을 반환합니다.

 

즉, 함수는 입력과 출력 사이의 관계일 뿐입니다. 각 입력은 정확히 하나의 출력을 가지지만, 그 출력이 반드시 고유할 필요는 없습니다. 왼편은 x에서 y로의 완벽하게 유효한 함수의 다이어그램입니다;

 

 

대조적으로, 오른편의 다이어그램은 입력값 5가 여러 출력을 가리키기 때문에 함수가 아닌 관계를 보여줍니다:

 

 

함수는 입력과 출력의 쌍 집합으로 설명될 수 있습니다: [(1,2), (3,6), (5,10)] (이 함수는 입력을 두 배로 하는 것처럼 보입니다).

또는 표로 표현할 수 있습니다:

Input Output
1 2
2 4
3 6

 

또는 x를 입력으로, y를 출력으로 하는 그래프로 표현할 수도 있습니다:

입력이 출력을 결정하면 구현 세부 사항은 필요하지 않습니다. 함수는 입력에서 출력으로의 매핑일 뿐이므로, 객체 리터럴을 작성하고 () 대신 []로 실행할 수 있습니다.

var toLowerCase = {
  'A': 'a',
  'B': 'b',
  'C': 'c',
  'D': 'd',
  'E': 'e',
  'F': 'f',
};

toLowerCase['C'];
//=> 'c'

var isPrime = {
  1: false,
  2: true,
  3: true,
  4: false,
  5: true,
  6: false,
};

isPrime[3];
//=> true

물론, 직접 계산하고 싶을 수도 있지만, 이는 함수를 다른 방식으로 생각하는 것을 보여줍니다. (여러 인수를 가진 함수는 어떨까요? 물론, 이는 수학적으로 생각할 때 불편함을 줍니다. 지금은 배열로 묶거나 arguments 객체를 입력으로 생각할 수 있습니다. 커링을 배우면 수학적 함수 정의를 직접 모델링할 수 있습니다.)

여기서 중요한 점은: 순수 함수는 수학적 함수이며, 이는 함수형 프로그래밍의 핵심입니다. 이러한 작은 천사들로 프로그래밍하는 것은 큰 이점을 제공할 수 있습니다. 순수성을 유지하기 위해 많은 노력을 기울이는 이유를 살펴보겠습니다.

순수성을 지키는 이유

캐시 가능

먼저, 순수 함수는 입력에 따라 항상 캐시될 수 있습니다. 이는 일반적으로 메모이제이션이라는 기법을 사용하여 수행됩니다:

var squareNumber = memoize(function(x) {
  return x * x;
});

squareNumber(4);
//=> 16

squareNumber(4); // 입력 4에 대한 캐시 반환
//=> 16

squareNumber(5);
//=> 25

squareNumber(5); // 입력 5에 대한 캐시 반환
//=> 25

다음은 간단한 구현이지만, 더 강력한 버전도 많이 있습니다.

var memoize = function(f) {
  var cache = {};

  return function() {
    var arg_str = JSON.stringify(arguments);
    cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
    return cache[arg_str];
  };
};

어떤 비순수 함수도 평가를 지연시켜 순수하게 만들 수 있습니다:

var pureHttpCall = memoize(function(url, params) {
  return function() {
    return $.getJSON(url, params);
  };
});
 

흥미로운 점은 실제 HTTP 호출을 하는 대신, 호출 시 특정 HTTP 호출을 할 함수를 반환한다는 것입니다. 이 함수는 동일한 입력에 대해 항상 동일한 출력을 반환하기 때문에 순수합니다: url과 params를 주어진 HTTP 호출을 수행할 함수입니다.

memoize 함수는 잘 작동하며, HTTP 호출의 결과를 캐시하지 않고, 생성된 함수를 캐시합니다.

이제까지는 크게 유용하지 않을 수 있지만, 곧 더 유용한 방법을 배울 것입니다. 중요한 점은 모든 함수를 순수하게 만들어 캐시할 수 있다는 것입니다.

이식 가능 / 자기 문서화

순수 함수는 완전히 독립적입니다. 함수가 필요로 하는 모든 것이 명시적으로 전달됩니다. 이는 어떻게 유리할까요? 우선, 함수의 의존성이 명시적이므로 더 쉽게 이해할 수 있습니다.

// 비순수
var signUp = function(attrs) {
  var user = saveUser(attrs);
  welcomeUser(user);
};

var saveUser = function(attrs) {
    var user = Db.save(attrs);
    ...
};

var welcomeUser = function(user) {
    Email(user, ...);
    ...
};

// 순수
var signUp = function(Db, Email, attrs) {
  return function() {
    var user = saveUser(Db, attrs);
    welcomeUser(Email, user);
  };
};

var saveUser = function(Db, attrs) {
    ...
};

var welcomeUser = function(Email, user) {
    ...
};
 

순수 함수는 의존성을 명확히 하며, 함수 시그니처만으로도 필요한 것들을 알 수 있습니다. 이는 프로그램을 이해하는 데 큰 도움이 됩니다.

순수 형태에서는 "의존성 주입"을 통해 데이터베이스나 메일 클라이언트를 인자로 전달하므로 더 유연한 애플리케이션을 만들 수 있습니다. 다른 데이터베이스를 사용하고 싶다면 함수 호출 시 이를 전달하면 됩니다.

자바스크립트 환경에서는 이식성이 함수 직렬화 및 소켓을 통한 전송을 의미할 수 있습니다. 이는 웹 워커에서 모든 애플리케이션 코드를 실행할 수도 있음을 의미합니다. 이식성은 강력한 특성입니다.

순수 함수는 공유 메모리에 접근할 필요가 없고, 부작용으로 인한 경쟁 상태가 없으므로 병렬로 실행할 수 있습니다.

요약

순수 함수가 무엇인지, 왜 함수형 프로그래머들이 순수함수를 중요하게 여기는지 살펴보았습니다. 앞으로 모든 함수를 순수하게 작성하려고 노력할 것입니다. 이를 위해 몇 가지 도구가 필요하지만, 그동안 순수한 코드와 그렇지 않은 코드를 분리해 보겠습니다.

순수 함수로 프로그래밍하는 것은 몇 가지 추가 도구 없이 다소 힘들 수 있습니다. 데이터를 인자로 전달해야 하고, 상태와 효과를 사용할 수 없습니다. 이러한 프로그래밍을 어떻게 할 수 있을까요? 다음 장에서는 커리를 배우며 이 문제를 해결해 보겠습니다.

'Functional Programming' 카테고리의 다른 글

mostly-adequate-guide-02  (1) 2024.07.16
mostly-adequate-guide-01  (2) 2024.07.16

2장: 일급 함수

빠른 복습

함수가 "일급"이라는 말은, 다른 것들과 다르지 않다는 뜻입니다. 즉, 일반 클래스와 같습니다. 우리는 함수를 다른 데이터 타입처럼 취급할 수 있으며, 그들에 대해 특별히 특별할 것은 없습니다. 함수는 배열에 저장될 수도 있고, 전달될 수도 있으며, 변수에 할당될 수도 있습니다.

이것은 자바스크립트의 기초이지만, GitHub에서 간단히 코드 검색을 해보면 이 개념을 회피하거나, 혹은 널리 무시하고 있다는 것을 알 수 있습니다. 가짜 예시를 하나 들어볼까요? 그렇게 해보죠.

var hi = function(name) {
  return 'Hi ' + name;
};

var greeting = function(name) {
  return hi(name);
};
 

여기서 greeting 함수의 hi를 감싼 부분은 완전히 중복입니다. 왜일까요? 자바스크립트에서 함수는 호출할 수 있기 때문입니다. hi가 끝에 ()가 있으면 실행되어 값을 반환합니다. 그렇지 않으면, 단순히 변수에 저장된 함수를 반환합니다. 확실히 하기 위해 확인해 보세요:

 
hi;
// function(name) {
//  return 'Hi ' + name
// }

hi('jonas');
// "Hi jonas"

greeting은 같은 인수로 hi를 호출할 뿐이므로, 단순히 다음과 같이 쓸 수 있습니다:

hi;
// function(name) {
//  return 'Hi ' + name
// }

hi('jonas');
// "Hi jonas"

즉, hi는 이미 하나의 인수를 기대하는 함수이므로, 동일한 인수로 hi를 호출하는 또 다른 함수를 둘러싸는 것은 말이 되지 않습니다. 그것은 7월 한가운데서 가장 무거운 파카를 입고 에어컨을 틀고 아이스바를 요구하는 것과 같습니다.

이것은 지나치게 장황하고, 평가를 지연시키기 위해 함수를 또 다른 함수로 둘러싸는 것은 나쁜 습관입니다. (이유는 곧 보겠지만, 유지보수와 관련이 있습니다.)

이해하기 어려운 부분이므로, 몇 가지 더 재미있는 예시를 살펴보겠습니다. npm 모듈에서 발굴된 것들입니다.

// 무지한 예시
var getServerStuff = function(callback) {
  return ajaxCall(function(json) {
    return callback(json);
  });
};

// 계몽된 예시
var getServerStuff = ajaxCall;

세상에는 이와 같은 ajax 코드가 널려 있습니다. 이 둘이 동일한 이유는 다음과 같습니다:

// 이 줄
return ajaxCall(function(json) {
  return callback(json);
});

// 이 줄과 같습니다
return ajaxCall(callback);

// 따라서 getServerStuff를 리팩토링합니다
var getServerStuff = function(callback) {
  return ajaxCall(callback);
};

// ...이것은 이와 같습니다
var getServerStuff = ajaxCall; // <-- 보세요, mum, 괄호가 없습니다

이것이 그렇게 하는 방법입니다. 다시 한 번 보죠, 왜 제가 이렇게 고집하는지 알 수 있습니다.

 
var BlogController = (function() {
  var index = function(posts) {
    return Views.index(posts);
  };

  var show = function(post) {
    return Views.show(post);
  };

  var create = function(attrs) {
    return Db.create(attrs);
  };

  var update = function(post, attrs) {
    return Db.update(post, attrs);
  };

  var destroy = function(post) {
    return Db.destroy(post);
  };

  return {
    index: index,
    show: show,
    create: create,
    update: update,
    destroy: destroy,
  };
})();

이 우스꽝스러운 컨트롤러는 99%가 쓸모없는 부분입니다. 우리는 다음과 같이 다시 쓸 수 있습니다:

var BlogController = {
  index: Views.index,
  show: Views.show,
  create: Db.create,
  update: Db.update,
  destroy: Db.destroy,
};

...혹은 아예 없애버릴 수도 있습니다. 이는 Views와 Db를 묶는 것 외에는 아무것도 하지 않기 때문입니다.

왜 일급 함수를 선호해야 하는가?

이제 일급 함수를 선호해야 하는 이유를 살펴보겠습니다. getServerStuff와 BlogController 예시에서 보았듯이, 실제로는 아무런 가치도 없고 유지보수와 검색을 위한 코드만 늘리는 간접층을 추가하기 쉽습니다.

게다가, 우리가 불필요하게 감싸고 있는 함수가 변경되면, 래퍼 함수도 변경해야 합니다.

var BlogController = {
  index: Views.index,
  show: Views.show,
  create: Db.create,
  update: Db.update,
  destroy: Db.destroy,
};

httpGet이 에러를 보낼 가능성이 생기면, 우리는 "접착제"를 변경해야 합니다.

 
// 애플리케이션의 모든 httpGet 호출로 돌아가서 명시적으로 err을 전달합니다.
httpGet('/post/2', function(json, err) {
  return renderPost(json, err);
});

일급 함수로 작성했더라면, 변경해야 할 부분이 훨씬 적었을 것입니다:

 
// renderPost는 httpGet 내에서 원하는 만큼의 인수를 사용하여 호출됩니다.
httpGet('/post/2', renderPost);

불필요한 함수를 제거하는 것 외에도, 우리는 인수를 이름짓고 참조해야 합니다. 이름에는 약간의 문제가 있습니다. 잠재적 오해의 소지가 있기 때문입니다 - 특히 코드베이스가 오래되고 요구사항이 변경될 때 그렇습니다.

프로젝트에서 동일한 개념에 여러 이름을 사용하는 것은 흔한 혼란의 원인입니다. 또한 일반적인 코드의 문제도 있습니다. 예를 들어, 이 두 함수는 정확히 같은 일을 하지만, 하나는 훨씬 더 일반적이고 재사용 가능하다고 느껴집니다:

// 현재 블로그에 특화됨
var validArticles = function(articles) {
  return articles.filter(function(article) {
    return article !== null && article !== undefined;
  });
};

// 미래의 프로젝트에 훨씬 더 관련됨
var compact = function(xs) {
  return xs.filter(function(x) {
    return x !== null && x !== undefined;
  });
};

특정한 명명을 사용함으로써, 우리는 특정 데이터(articles)에 스스로를 묶어버린 것처럼 보입니다. 이는 꽤 자주 발생하며, 많은 재발명의 원인이 됩니다.

객체 지향 코드와 마찬가지로, 여러분의 목을 물어뜯는 this를 조심해야 한다는 점을 언급해야 합니다. 기본 함수가 this를 사용하고 우리가 일급 함수로 호출하면, 우리는 이 누출되는 추상화의 분노에 노출됩니다.

 
var fs = require('fs');

// 무서운 예시
fs.readFile('freaky_friday.txt', Db.save);

// 덜 무서운 예시
fs.readFile('freaky_friday.txt', Db.save.bind(Db));

Db가 스스로에 묶여 있으면, 프로토타입적 쓰레기 코드를 자유롭게 접근할 수 있습니다. 저는 더러운 기저귀처럼 this를 사용하는 것을 피합니다. 함수형 코드를 작성할 때는 정말 필요하지 않습니다. 그러나 다른 라이브러리와 인터페이스할 때는, 우리 주변의 미친 세상에 순응해야 할 것입니다.

어떤 사람들은 this가 속도에 필요하다고 주장할 것입니다. 미세 최적화를 좋아하는 타입이라면, 이 책을 닫으십시오. 돈을 돌려받을 수 없다면, 더 복잡한 무언가로 교환할 수 있을 것입니다.

이제, 우리는 다음으로 넘어갈 준비가 되었습니다.

'Functional Programming' 카테고리의 다른 글

mostly-adequate-guide-03  (0) 2024.07.24
mostly-adequate-guide-01  (2) 2024.07.16

1장: 우리는 무엇을 하고 있는가?

소개

안녕하세요! 저는 프랭클린 리스비 교수입니다. 만나서 반갑습니다. 앞으로 우리가 함께 할 시간이 있을 겁니다. 저는 여러분에게 함수형 프로그래밍에 대해 조금 가르쳐야 합니다. 하지만 저에 대해서는 이쯤 하고, 여러분에 대해서는 어떤가요? 여러분이 자바스크립트 언어에 익숙하고, 약간의 객체 지향 경험이 있으며, 스스로를 평범한 프로그래머라고 생각하길 바랍니다. 곤충학 박사 학위가 필요하지는 않습니다. 버그를 찾고 고치는 방법만 알면 됩니다.

함수형 프로그래밍에 대한 사전 지식을 가정하지는 않겠습니다. 왜냐하면 우리가 잘 알고 있듯이, 가정하면 어떤 일이 벌어질지 알기 때문입니다. 하지만 저는 여러분이 변경 가능한 상태, 무제한적인 부작용, 원칙 없는 설계로 인해 발생하는 불리한 상황을 경험해 보았을 것이라고 기대합니다. 이제 제대로 소개를 했으니, 본격적으로 시작해 보겠습니다.

이 장의 목적은 함수형 프로그램을 작성할 때 우리가 추구하는 것이 무엇인지 감을 잡게 하는 것입니다. 프로그램을 함수형으로 만드는 것이 무엇인지 알지 못하면, 객체를 피하려고 무작정 낙서를 하게 될 것이고, 그것은 매우 어색한 일이 될 것입니다. 우리는 우리의 코드를 향해 던질 과녁이 필요하며, 물결이 거칠어질 때 우리의 길을 안내할 천체 나침반이 필요합니다.

프로그래밍의 일반적인 원칙들 - DRY(반복하지 말라), YAGNI(당신은 그것이 필요하지 않을 것이다), 낮은 결합도 높은 응집도, 최소한의 놀라움의 원칙, 단일 책임 원칙 등 - 이 있습니다. 저는 수년간 들어온 모든 지침들을 나열하지는 않겠습니다. 요점은 이 원칙들이 함수형 환경에서도 유효하다는 것입니다. 하지만 그것들은 우리의 목표에 있어 단지 부수적인 것들일 뿐입니다. 이제 우리는 키보드를 두드릴 때의 의도를 이해하고, 함수형 이상향을 추구해 보려 합니다.

짧은 만남

약간의 미친 짓으로 시작해 봅시다. 여기 갈매기 애플리케이션이 있습니다. 무리가 합쳐지면 더 큰 무리가 되고, 번식할 때는 번식하는 갈매기의 수만큼 증가합니다. 이제 이 코드는 객체 지향적으로 잘 작성된 코드가 아님을 명심하세요. 이 코드는 현대의 할당 기반 접근 방식의 위험성을 강조하기 위해 존재합니다. 보시죠:

 
var Flock = function(n) {
  this.seagulls = n;
};

Flock.prototype.conjoin = function(other) {
  this.seagulls += other.seagulls;
  return this;
};

Flock.prototype.breed = function(other) {
  this.seagulls = this.seagulls * other.seagulls;
  return this;
};

var flock_a = new Flock(4);
var flock_b = new Flock(2);
var flock_c = new Flock(0);

var result = flock_a.conjoin(flock_c)
    .breed(flock_b).conjoin(flock_a.breed(flock_b)).seagulls;
//=> 32

이런 끔찍한 괴물을 누가 만들었을까요? 내부 상태가 계속 변하는 것을 추적하는 것은 매우 어렵습니다. 게다가 답이 틀렸습니다! 원래 16이어야 했지만, flock_a는 과정에서 영구적으로 변경되었습니다. 불쌍한 flock_a. 이는 IT의 무정부 상태입니다! 이것은 야생 동물 산수입니다!

이 프로그램을 이해하지 못해도 괜찮습니다, 저도 이해하지 못합니다. 요점은 상태와 변경 가능한 값이 따라가기 어렵다는 것입니다, 심지어 이렇게 작은 예제에서도요.

더 함수형 접근 방식으로 다시 시도해 봅시다:

var conjoin = function(flock_x, flock_y) { return flock_x + flock_y; };
var breed = function(flock_x, flock_y) { return flock_x * flock_y; };

var flock_a = 4;
var flock_b = 2;
var flock_c = 0;

var result = conjoin(
  breed(flock_b, conjoin(flock_a, flock_c)), breed(flock_a, flock_b)
);
//=>16

이번에는 정답을 얻었습니다. 코드도 훨씬 적습니다. 함수 중첩이 약간 혼란스럽지만...(이 문제는 5장에서 해결할 것입니다). 더 나아졌지만, 더 깊이 파고들어 봅시다. 스페이드를 스페이드라 부르는 것의 이점이 있습니다. 그렇게 했다면 우리가 단순한 덧셈(conjoin)과 곱셈(breed)을 다루고 있다는 것을 알 수 있었을 것입니다.

이 두 함수에는 이름 외에는 특별한 것이 없습니다. 우리의 사용자 정의 함수를 그들의 진짜 정체로 이름을 바꿔 봅시다.

var add = function(x, y) { return x + y; };
var multiply = function(x, y) { return x * y; };

var flock_a = 4;
var flock_b = 2;
var flock_c = 0;

var result = add(
  multiply(flock_b, add(flock_a, flock_c)), multiply(flock_a, flock_b)
);
//=>16

이로써 우리는 고대의 지식을 얻었습니다:

// 결합법칙
add(add(x, y), z) === add(x, add(y, z));

// 교환법칙
add(x, y) === add(y, x);

// 항등원
add(x, 0) === x;

// 분배법칙
multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));

아, 그렇습니다. 이 오래된 신뢰할 수 있는 수학적 특성들은 우리에게 유용할 것입니다. 이 특성들을 바로 기억해내지 못해도 걱정하지 마세요. 우리 중 많은 사람들이 이 정보를 검토한 지 오래되었을 것입니다. 이 특성을 사용하여 우리의 작은 갈매기 프로그램을 단순화할 수 있는지 봅시다.

// 원래의 코드
add(multiply(flock_b, add(flock_a, flock_c)), multiply(flock_a, flock_b));

// 항등원 특성을 적용하여 불필요한 add 제거
// (add(flock_a, flock_c) == flock_a)
add(multiply(flock_b, flock_a), multiply(flock_a, flock_b));

// 분배법칙을 적용하여 결과를 얻음
multiply(flock_b, add(flock_a, flock_a));

훌륭합니다! 우리는 호출 함수 외에 한 줄의 사용자 정의 코드도 작성할 필요가 없었습니다. 우리는 완전성을 위해 여기 add와 multiply 정의를 포함하지만, 실제로는 이들을 작성할 필요가 없습니다 - 이미 작성된 라이브러리에 add와 multiply가 있을 것입니다.

여러분은 "이렇게 수학적인 예제를 앞에 두다니 얼마나 교활한가" 혹은 "실제 프로그램은 이렇게 단순하지 않아서 이런 방식으로 논리적으로 접근할 수 없다"고 생각할 수 있습니다. 이 예제를 선택한 이유는 대부분의 사람들이 덧셈과 곱셈에 대해 이미 알고 있어서 수학이 여기서 우리에게 얼마나 유용할 수 있는지를 쉽게 이해할 수 있기 때문입니다.

실망하지 마세요 - 이 책 전반에 걸쳐 카테고리 이론, 집합 이론, 람다 계산법을 조금씩 섞어 사용하여 우리의 갈매기 예제와 같은 단순함과 결과를 달성하는 실제 예제를 작성할 것입니다. 여러분이 수학자가 될 필요는 없습니다. 일반적인 프레임워크나 API를 사용하는 것처럼 느껴질 것입니다.

함수형 프로그래밍의 아날로그와 같은 방식으로 일상적인 애플리케이션을 작성할 수 있다는 사실에 놀랄 수 있습니다. 우리는 확고한 특성을 가진 프로그램을 작성할 수 있습니다. 간결하면서도 이해하기 쉬운 프로그램을 작성할 수 있습니다. 매번 바퀴를 재발명하지 않는 프로그램을 작성할 수 있습니다. 무법은 범죄자에게는 좋지만, 이 책에서는 수학의 법칙을 인정하고 준수하고자 합니다.

모든 조각이 매우 정중하게 맞아 떨어지는 이론을 사용하고자 합니다. 우리는 우리의 특정 문제를 일반적이고 조합 가능한 조각들로 표현하고 그 특성을 이용하여 우리에게 유리하게 활용하고자 합니다. 이는 절제되지 않은 명령형 프로그래밍 접근 방식보다는 더 많은 규율을 요구할 것입니다 (명령형의 정확한 정의는 책 후반부에서 다룰 것이지만, 지금은 함수형 프로그래밍이 아닌 모든 것으로 간주합니다). 그러나 원칙적이고 수학적인 프레임워크 내에서 작업하는 보상은 여러분을 놀라게 할 것입니다.

우리는 함수형 북극성을 잠깐 보았지만, 본격적으로 여정을 시작하기 전에 몇 가지 구체적인 개념을 이해할 필요가 있습니다.

https://github.com/enshahar/mostly-adequate-guide-kr/blob/master/ch1.md

'Functional Programming' 카테고리의 다른 글

mostly-adequate-guide-03  (0) 2024.07.24
mostly-adequate-guide-02  (1) 2024.07.16

+ Recent posts