#Programming

Composing Programs - 2

2026.04.13

Elements of Programming

프로그래밍 언어는 단순히 컴퓨터에게 작업을 수행하도록 지시하는 수단 그 이상이다. 프로그래밍 언어는 또한, 계산 과정에 대한 아이디어를 체계화하는 틀의 역할을 한다. 따라서 프로그램은 사람이 읽을 수 있도록 작성되어야 하며, 기계가 실행할 수 있도록 하는 것은 부수적인 목적이다.

언어를 설명할 때 우리는 단순한 아이디어를 결합하여 더 복잡한 아이디어를 형성하는 데 언어가 제공하는 수단에 특히 주의를 기울여야 한다. 모든 강력한 언어에는 다음과 같은 3가지 메커니즘이 있다.

  • 언어가 제공하는 가장 단순한 구성 요소인 기본 표현식과 문 (statements),
  • 더 단순한 요소들로 복합 요소를 구축하는 결합 수단
  • 복합 요소에 이름을 붙이고 하나의 단위로 조작할 수 있게 해주는 추상화 수단.

프로그래밍에서는 함수와 데이터라는 두 가지 종류의 요소를 다룬다. 곧 이 둘이 실제로는 그렇게 별개라는 사실을 알게 되겠지만, 조금 말해보자면, 데이터는 우리가 조작하고자 하는 대상이며 함수는 데이터를 조작하는 규칙을 기술한다.

따라서 강력한 프로그래밍 언어는 기본 데이터와 기본 함수를 기술할 수 있을 뿐만 아니라, 함수와 데이터를 결합하고 추상화하는 몇가지 방법을 갖추고 있어야 한다.

Expressions

먼저 기본 표현식에 대해 알아보자. 기본 표현식의 한 종류는 숫자이다. 더 정확하게 말하면 우리가 입력하는 표현식은 10진수로 숫자를 나타내는 숫자들로 구성된다.

숫자를 나타내는 표현식은 수학 연산자와 결합되어 복합적인 표현식을 형성할 수 있으며 인터프리터는 이를 계산한다.

이러한 수학적 표현식은 중위 표기법을 사용하며, 연산자가 피연산자 사이에 위치한다. 파이썬에는 복합 표현식을 구성하는 다양한 방법들이 있다.

Call Expressions

가장 중요한 복합 표현식은 호출 표현식이며, 이는 함수를 여러 인자에 적용시킨다. 대수학에서 배운 바와 같이, 수학적 함수란 입력 인자들을 출력 값으로 매핑하는 것이다.

예를 들어, max 함수는 입력값들 중 가장 큰 값으로 매핑한다. 파이썬에서 함수 적용을 표현하는 방식은 일반적인 수학과 동일하다.

이 호출 표현식에는 하위 표현식이 있다. 연산자는 괄호 앞에 오는 표현식이며, 괄호 안에는 쉼표로 구분된 피연선자 표현식 목록이 들어있다.

연산자는 함수를 지정한다. 이 호출 표현식이 계산되면, 함수 max가 인자 7.5와 9.5를 받아 호출되고, 9.5라는 값을 반환한다고 말한다.

호출 표현식에서 인수의 순서는 중요하다. 예를 들어, pow 함수는 첫번째 인수들 두번째 인수의 지수로 승산한다.

함수 표기법은 중위 표기법이라는 수학적 관례에 비해 세가지 주요 장점이 있다.

  1. 함수는 임의의 개수의 인자를 받을 수 있다.

함수 이름이 항상 인자보다 앞에 오기 때문에 모호성이 발생할 수 없다.

  1. 함수 표기법은 요소 자체가 복합 표현식인 중첩된 표현식으로 직관적으로 확장된다. 중첩된 호출 표현식에서는 복합 중위 표현식과 달리 중첩 구조가 괄호 안에 완전히 명시적으로 나타난다.

파이썬 인터프리터가 평가할 수 있는 표현식의 중첩 깊이와 전체적인 복잡성에는 제한이 없다. 그러나 사람은 다중 레벨 중첩에 금방 혼란을 느끼게 된다.

프로그래머로서 사람의 중요한 역할은 표현식을 구조화하여 본인과 프로그래밍 파트너 그리고 미래에 내 표현식을 읽을지도 모르는 다른 사람들도 이해할 수 있도록 하는것이다.

  1. 수학 표기법은 매우 다양한 형태를 띤다. 곱셉은 항 사이에 나타나고, 지수는 위첨자로, 나눗셈은 가로줄로, 제곱근은 루트모양으로 나타난다. 이러한 표기법 중 일부는 입력하기가 매우 어렵다. 그러나 이러한 모든 복잡성은 호출 표현식의 표기법을 통해 통합될 수 있다. 파이썬의 중위 표기법을 사용하여 일반적인 수학 연산자를 지원하지만, 어떤 연산자든 이름이 있는 함수로 표현할 수 있다.

Importing Library Functions

파이썬은 앞에서 언급한 연산자 함수를 포함하여 매우 많은 수의 함수를 정의하고 있지만, 기본적으로 모든 함수 이름을 사용할 수 있게 하지는 않는다.

대신, 파이썬은 자신이 알고 있는 함수와 기타 요소들을 모듈로 구성하며, 이 모듈들이 모여 파이썬 라이브러리를 이룬다. 이러한 요소들을 사용하려면 해당 모듈을 임포트해야 한다.

예를 들어 math 모듈은 다음과 같이 다양한 익숙한 수학 함수를 제공한다.

또한 operator 모듈은 중위 연산자에 해당하는 함수들을 제공한다.

imort 문은 모듈 이름을 지정한 다음 해당 모듈에서 가져올 명명된 속성을 나열한다. 함수가 한 번 import되면 여러 번 호출할 수 있다.

이러한 연산자 함수를 사용하는 것과 연산자 기호 자체를 사용하는 것 사이에는 차이가 없다. 일반적으로 대부분의 프로그래머는 간단한 산술 연산을 표현할 때 기호와 중위 표기법을 사용한다.

Python3 라이브러리 문서에는 math 모듈과 같이 각 모듈에서 정의된 함수들이 나열되어 있다. 하지만 이 문서는 언어 잘체를 잘 알고 있는 개발자를 위해 작성되어있다.

당분간은 문서을 읽는 것보다 함수를 직접 사용해보는 것이 그 동작을 더 잘 이해하는데 도움이 될 수 있다.

Names and the Environment

프로그래밍 언어의 핵심적인 측면 중 하나는 계산 객체를 참조하기 위해 이름을 사용하는 방법을 제공하는 것이다. 값에 이름이 부여된 경우, 그 이름이 해당 값에 바인딩 되었다고 말한다.

파이썬에서는 = 왼쪽에 이름을, 오른쪽에 값을 두는 할당문을 사용하여 새로운 바인딩을 설정할 수 있다.

이름은 import 문을 통해서도 바인딩된다.

= 기호는 파이썬에서 할당 연산자라고 한다. 할당은 가장 단순한 추상화 수단이다. 왜냐하면 이를 통해 위에서 계산한 면적과 같은 복합 연산의 결과를 간단한 이름으로 참조할 수 있기 때문이다. 이러한 방식으로 복잡한 프로그램은 점점 더 복잡해지는 계산 객체를 단계적으로 구축함으로써 만들어진다.

이름을 값에 바인딩하고 나중에 그 이름을 통해 값을 검색할 수 있다는 것은, 인터프리터가 이름, 값 그리고 바인딩 관계를 추적하는 일종의 메모리를 유지해야 함을 의미한다. 이 메모리를 환경(Environment)이라고 한다.

이름은 함수에 바인딩 될 수도 있다. 예를 들어, max라는 이름은 우리가 지금까지 사용해 온 max 함수에 바인딩 되어있다. 함수는 숫자와 달리 텍스트로 표현하기 까다로우므로, 파이썬은 함수를 설명해 달라는 요청을 받으면 대신 식별 가능한 설명을 출력한다.

대입문을 사용하여 기존 함수에 새로운 이름을 부여할 수 있다.

또한 연속적인 할당문을 통해 이름을 새로운 값에 다시 바인딩할 수 있다.

파이썬에서 이름은 프로그램 실행 과정에서 서로 다른 값에 바인딩 될 수 있기 때문에 종종 변수 이름 또는 변수라고 불린다. 할당을 통해 이름이 새로운 값에 바인딩되면, 더 이상 이전의 어떤 값에도 바인딩 되어있지 않는다. 내장된 이름조차도 새로운 값에 바인딩 할 수 있다.

max에 5를 할당한 후에는 max라는 이름이 더 이상 함수와 연결되어 있지 않으므로, max(2, 3, 4)를 호출하려고 하면 오류가 발생한다.

할당문을 실행할 때, 파이썬은 왼쪽의 이름에 대한 바인딩을 변경하기 전에 = 오른쪽의 식을 평가한다. 따라서 할당문에 의해 바인딩 될 이름이라 하더라도 오른쪽 식에서 해당 이름을 참조할 수 있다.

또한 단일 문장에서 여러 이름에 여러 값을 할당할 수 있으며 이때 =의 왼쪽에 있는 이름과 =의 오른쪽에 있는 표현식은 쉼표로 구분된다.

한 변수의 값을 변경해도 다른 변수에는 영향을 미치지 않는다. 아래 예시에는 area라는 변수는 원래 반지름을 기준으로 정의된 값에 할당되었지만, area 값은 변경되지 않았다. area 값을 업데이트 하려면 별도의 할당문이 필요하다.

다중 할당에서는 = 오른쪽의 모든 식이 평가된 후에야 왼쪽의 변수들이 해당 값에 할당된다. 이 규칙 덕분에 두 변수에 할당된 값을 한 줄의 문장으로 바꿀 수 있다.

Evaluating Nested Expressions

이 챕터의 목표 중 하나는 절차적 사고와 관련된 문제들을 명확히 구분하는 것이다. 대표적인 예로 중첩된 호출 표현식을 평가할 때 인터프리터 자체가 하나의 절차를 따르고 있다는 점을 살펴보자.

호출 표현식을 평가하기 위해 파이썬은 다음과 같은 과정을 거친다.

  1. 연산자 및 피연산자 하위 표현식을 평가한 다음,
  2. 연산자 하위 표현식의 값인 함수를 피연산자 하위 표현식의 값인 인자에 적용한다.

이 간단한 절차조차도 일반적인 프로세스에 관한 몇 가지 중요한 점을 보여준다.

첫번째 단계는 호출 표현식의 평가 과정을 완료하기 위해 먼저 다른 표현식들을 평가해야 함을 시사한다. 따라서 이 평가 절차는 본질적으로 재귀적이다. 즉 이 절차의 단계 중 하나로 규칙 자체를 호출해야 할 필요가 포함되어있다.

예를 들어, 다음을 평가하려면

이 평가 절차를 4번 적용해야 한다. 평가하는 각 표현식을 그림으로 그려보면 이 과정의 계층적 구조를 시각화 할 수 있다.

이 그림은 표현식 트리라고 한다. 컴퓨터 과학에서 트리는 통상적으로 위에서 아래로 자란다. 트리의 각 지점에 있는 객체를 노드라고 하며, 이 경우 노드는 값과 짝을 이룬 표현식들이다.

루트, 즉 맨 위에 있는 전체 표현식을 평가하려면 먼저 그 하위 표현식인 가지들을 평가해야 한다. 잎 표현식(갈래가 더이상 뻗어나가지 않는 노드)은 함수나 숫자를 나타낸다.

내부 노드는 두 부분으로 구성된다. 평가 규칙이 적용되는 호출 표현식과 그 표현식의 결과.

이 트리를 통해 살펴보면, 피연산자의 값이 말단 노드에서 시작하여 위쪽으로 퍼져 나가며, 점점 더 높은 수준에서 결합된다고 상상할 수 있다.

다름으로, 첫 번째 단계를 반복적으로 적용하다 보면, 호출 표현식이 아니라 숫자나 이름과 같은 원시 표현식을 평가해야 하는 지점에 도달하게 된다. 우리는 다음과 같이 규정함으로써 원시 표현식의 경우를 처리한다.

숫자는 그 이름이 지칭하는 숫자로 평가되고, 이름은 현재 환경에서 그 이름과 연관된 값으로 평가된다. 표현식 내 기호의 의미를 결정하는 데 있어 환경이 중요한 역할을 한다는 점을 유의해야 한다.

파이썬에서 다음과 같은 표현식의 값에 대해 이야기 하는 것은

x라는 이름에 의미를 부여할 환경 정보를 명시하지 않고서는 무의미하다. 환경은 평가가 이루어지는 맥락을 제공하며, 이는 프로그램 실행을 이애하는 데 중요한 역할을 한다.

이 평가 절차만으로는 모든 파이썬 코드를 계산할 수 없으며, 호출 표현식, 숫자, 이름만 평가할 수 있다. 예를 들어, 할당문은 처리하지 못한다.

위 코드를 실행하면 반환하지도 않고, 어떤 인자에 대해 함수를 계산하지도 않는다. 왜냐하면 할다의 목적은 이름과 값을 연결하는 것이기 때문이다. 일반적으로 할당문은 평가되지 않고 실행되며, 값을 산출하기보다는 어떤 변화를 일으킨다. 각 표현식이나 할당문 유형에는 고유한 평가 또는 실행 절차가 있다.

"숫자가 숫자로 계산된다"고 말할 때 실제로 인터프리터가 숫자를 숫자로 계산한다는 의미이다. 프로그래밍 언어에 의미를 부여하는 것은 인터프리터이다. 인터프리터는 항상 일관되게 동작하는 고정된 프로그램이므로, 파이썬 프로그램의 맥락에서 숫자 자체가 값으로 계산된다고 말할 수 있다.

The Non-Pure Print Function

여기서는 2가지 유형의 함수를 구분해서 설명하겠다.

Pure functions : 입력을 받아 출력을 반환하는 함수

내장함수는 입력을 받아 출력을 생성하는 작은 기계로 비유할 수 있다.

abs 함수는 pure 함수이다 pure 함수는 호출 시 값을 반환하는 것 외에는 어떠한 영향도 미치지 않는다는 특성을 가진다. 또한, pure 함수는 동일한 인자로 두 번 호출되었을 때 항상 동일한 값을 반환해야 한다.

Non pure functions : 비순수 함수를 적용하면 값을 반환하는 것 외에도, 인터프리터나 컴퓨터의 상태를 변경하는 부수 효과를 발생시킬 수 있다. 일반적인 부수 효과로는 print 함수를 사용하여 반환 값 이외에 추가적인 출력을 생성하는 것이 있다.

이 예제에서 print와 abs는 비슷해 보일 수 있지만, 작동 박식은 근본적으로 다르다. print가 반환하는 값은 항상 None이며, 이는 아무것도 나타내지 않는 파이썬의 특수값이다. 대화형 파이썬 인터프리터는 None 값을 자동으로 출력하지 않는다. print의 경우 함수 자체가 호출되는 것의 부수 효과로 출력을 생성하고 있다.

Print 호출이 중첩된 표현식은 이 함수가 순수하지 않음을 보여준다.

이 결과를 예상하지 못했다면, 표현식 트리를 그려 이 표현식을 평가했을 때 왜 이런 결과가 나오는지 파악해봐라.

print는 결국 None을 반환하기 때문에 내부 하위표현식이 둘 다 None을 반환해 결국 None이 2번 출력되는 것이다. 이러한 사실은 print가 할당문에서 표현식으로 사용되어서는 안 된다는 것을 의미한다.

순수 함수는 부수 효과를 일으키거나 시간이 지남에 따라 동작이 변할 수 없다는 점에서 제한을 받는다. 이러한 제한을 두면 상당한 이점을 얻을 수 있다.

첫째, 순수 함수는 복합 호출 표현식으로 더 안정적으로 조합될 수 있다. 위에 비순수 함수 예제에서 볼 수 있듯이, print는 피 연산자 표현식에서 사용될 때 유용한 결과를 반환하지 않는다. 반면, max pow sqrt와 같은 함수는 중첩된 표현식에서 효과적으로 사용할 수 있다.

둘째,순수 함수는 테스트하기 더 간단한 경향이 있다. 인자 목록은 항상 동일한 반환값을 생성하므로, 이를 예상 반환 값과 비교할 수 있다.