삽질의 현장/- ETC

프로그램을 작성하는 33가지 방법 - Chaptor 08 정리

shovelman 2019. 3. 23. 19:43

최근 '프로그래밍 패턴 (프로그램을 작성하는 33가지 방법)' 을 읽기 시작했다.

한 가지의 기능을 Chaptor 마다 각각 다른 패턴으로 구현하여 설명한 책이다.

모두 비슷하게 동작하지만 환경과 상황이라는 제약 조건 안에서 탄생된 패턴을 직접 코드로 보여주고 있어직접 코딩을 하며 이해하고 있는 중이다.

책에서 소개하는 한 가지 기능은 '텍스트 파일을 읽어 단어 빈도 출력' 하는 프로그램이다.


가장 빈도가 높은 단어 순으로 그에 해당하는 빈도를 내림차순으로 출력하는 기능을 구현하고 있다.

이전 Chaptor 보기 :프로그램을 작성하는 33가지 방법 - Chaptor 07 정리


Chaptor 08. 앞으로 차기

  • 각 함수에서 추가 매개변수를 취하는데, 맨 마지막 매개변수는 일반적으로 다른 함수를 사용한다.
  • 현재 함수 처리 마지막에 그 함수 매개변수를 적용한다.
  • 현재 함수의 출력을 그 함수 매개변수의 입력으로 사용한다.
  • 규모가 큰 문제를 함수의 파이프라인으로 해결한다. 이때 다음에 적용할 함수를 현재 함수의 매개변수로 사용한다.
  • 1970년대 이러한 연속성을 처음 사용하기 시작했다. (1964년 해당 내용이 기술되었지만 당시 사람들이 이해하지 못했다고 한다.)


연속 전달 형식(CPS; continuation-passing style)

함수 별 추가 매개변수로 함수 하나를 받는다. (맨 마지막에 호출, 대개 현재 함수의 반환값을 전달함)

함수는 자신의 호출자에게 값을 반환하지 않는 대신 다른 함수를 계속해서 실행한다.

주로 연속성에는 흔히 명명된 함수보다는 람다(lambda)라고 하는 익명 함수를 사용한다.

def read_file(path_to_file, func):
    with open(path_to_file) as f:
        data = f.read()
    func(data, normalize)


최초 호출은 '공장'의 모든 단계를 완전히 정의할 수 있는 함수 호출 사슬이다.

read_file(sys.argv[1], filter_chars)


시스템 설계 관점에서 본 이러한 형식

주로 컴파일러 최적화 목적으로 사용한다. 

일부 컴파일러에서는 컴파일하는 프로그램을 이 형식을 사용하는 중간 표현으로 변형함으로써 꼬리 호출(tail call)에 최적화할 수 있다.

꼬리 호출이란?

함수에서 가장 마지막 행동으로 취하는 함수 호출이다. 

해당 함수 꼬리 위치에서 하는 재귀 호출이며, 언어 처리기에서는 이전 호출의 스택 기록을 모두 안전하게 제거할 수 있다. (꼬리 재귀 최적화)

def f(data):
	if data == []:
			a() # f의 꼬리 위치
	else:
			b(data) # f의 꼬리 위치


다른 목적으로는 일반 사례와 실패 사례를 다루는 것이 있다. 

함수에서 해당 함수가 성공하거나 실패할 경우에 따라 계속 진행할 수 있게 일반 매개변수에 추가로 두 함수를 매개변수로 받는 것이 편리할 수 있다.

ex. ajax success, error ??

단일 스레드 언어에서 입출력(IO), 봉쇄(blocking)를 처리하는 것이다. 

해당 언어에서는 프로그램이 IO 연산을 할 때까지 절대 blocking하지 않으며, 어느 시점이 되면 제어를 해당 프로그램의 다른 명령으로 전달한다. 

이는 함수에 하나 이상의 추가 함수 인자를 전달해 처리한다.

ex. node.js에서 사용하는 웹소켓용 자바스크립트 라이브러인 socket.io 예제 일부

function handler (req, res) {
	fs.readFile(__dirname + '/index.html',
	function (err, data) {
		if (err) {
			res.writeHead(500);
			return end('Error loading index.html');
		}

		res.writeHead(200);
		res.end(data);
	});
}

readFile 함수는 원칙적으로 디스크에서 데이터를 읽을 때까지 스레드를 blocking 한다.

스레드가 없다면 해당 디스크 연산을 완료할 때 까지 아무런 요청을 처리할 수 없음을 의미한다.

자바스크립트의 설계 원칙은 비동기화인데, 디스크 연산으로 blocking 되더라도 응용 프로그램은 다음 명령을 계속 진행한다.