1.1 프로그래밍의 기본 요소
프로그래밍의 기본 요소
- 원시 표현식 : 언어와 관련한 가장 단순한 개체(entity)
- 조합 수단 : 단순한 요소들로부터 복합적인 요소를 만듦
- 추상화 수단 : 복합적인 요소들에 이름을 붙여서 하나의 단위로 다룸
모든 강력한 프로그래밍 언어는 반드시 원시 데이터
와 원시 함수
를 서술하는 기능이 있어야 하고, 그런 함수들과 데이터를 조합하고 추상화
하는 수단들도 제공해야 한다.
표현식
표현식 문장은 표현식(expression)과 세미콜론(;)으로 구성된다. 표현식은 하나 이상의 원시 표현식(primitive expression)으로 구성되는데, 여러 원시 표현식 중 하나로 수(number)가 있다.
수를 나타내는 표현식들은 연산자
(+나 *같은)로 조합할 수 있다. 그 결과는 연산자들에 해당하는 원시 함수를 해당 수들에 적용하는 하나의 복합 표현식이다. 이처럼 다른 표현식을 구성요소로 담고 있는 표현식을 가리켜 조합이라고 부른다. 가운데에 연산자가 있고, 양 옆에 피연산자가 있는 조합을 연산자 조합
이라고 부른다. 연산자를 두 피연산자 사이에 배치하는 관례를 중위 표기법이라고 부른다.
137 + 349;
JavaScript - 문장(Statement)과 표현식(Expression)의 차이점
💡 아하 모먼트 : 실수 2.00이 정수 2와 다른가?
다른 프로그래밍 언어와 달리, JavaScript는 실수와 정수 모두 Number
라는 하나의 데이터 타입만 사용합니다.
JavaScript의 기본적인 연산 - 숫자와 연산자 - Web 개발 학습하기 | MDN
JavaScript Number
타입은 Java 혹은 C#의 double
타입처럼 IEEE 754 64비트 바이너리 배정 밀도값(부동 소수점)입니다. JavaScript 코드에서 37과 같은 숫자 리터럴은 정수가 아니라 부동 소수점 값입니다. 일상적으로 흔히 사용되는 별도의 정수형은 없습니다. (JavaScript에는 이제 BigInt
타입이 있지만 일상적인 사용을 위해 Number를 대체하도록 설계되지 않았습니다. 37은 여전히 Number
일 뿐, BigInt
가 아닙니다.)
IEEE 754 64비트 바이너리 배정 밀도란?
아래의 포스트에서 잘 정리해주셔서, 링크로 대신합니다.
이름 붙이기와 환경
계산적 객체(프로그래밍)에 이름을 붙여서 이름으로 그 객체를 지칭하는 수단들은 프로그래밍 언어의 필수 기능에 해당한다. 상수의 이름은 해당 객체의 값을 지칭하는 용도로 쓰인다.
const foo = 'foo';
복합적인 연산의 결과를 간단한 이름을 지칭할 수 있다는 점에서, 상수 선언
은 우리의 언어에서 가장 단순한 추상화 수단
이다. 추상화의 특징으로 인하여 프로그램의 점진적 개발 및 테스트가 원활해진다. 이름과 값을 연관시키고 이름으로부터 값을 조회할 수 있으려면 이름-객체 쌍들을 저장하고 관리하는 특정한 메모리 공간을 갖추어야 한다. 그러한 메모리 공간을 환경
이라고 부른다.
연산자 조합의 평가
주어진 연산자 조합의 평가 과정을 완료하기 위해서는 먼저 조합의 각 피연산자를 평가해야 한다. 이는 한 단계에서 규칙 자신을 수행하므로 재귀적
다. 만약 재귀가 없었다면 평가 과정을 상당히 복잡하게 서술해야 했을 것이다. 재귀는 아래와 같은 트리 형태의 위계구조로 조직화된 객체를 다루는 데 대단히 강력한 기법이다.
(2 + 4 * 6) * (3 + 12)
복합 함수
이번 절에는 함수 선언(function declaration)을 살펴본다. 복합 연산에 이름을 붙여서 그 연산을 하나의 단위로 지칭하게 하는 함수 선언은 상수 선언보다 훨씬 강력한 추상화 기법
이다.
function square(x){
return x*x;
}
여기에서 square는 이름이 붙은 하나의 복합 함수
이다. 여기에는 x라는 지역 이름이 부여되는데, 이는 일상 언어의 대명사와 같은 역할을 한다. 이제 함수를 선언하였으니 이를 하나의 함수 적용 표현식
에서 사용할 수 있다.
square(21); //441
함수 적용 표현식을 다른 함수 적용의 인수 표현식으로 사용하는 것도 가능한다. 위처럼 복합 함수들 외에 모든 자바스크립트 환경은 해석기 자체에 내장된 또는 표준 라이브러리부터 적재한 원시 함수(method)
들도 제공하고 있다.
함수 적용의 치환 모형
함수 적용 평가는 먼저 해석기가 함수 적용의 요소들을 평가한 후 함수를 인수들에 적용한다. 원시 함수의 적용은 해석기 또는 라이브러리가 처리한다고 간주할 수 있다. 복합 함수의 적용 과정은 하나의 복합 함수를 인수들에 적용하기 위해, 함수의 각 매개변수를 해당 인수로 치환해서 함수의 반환 표현식을 평가한다.
아래와 같은 함수를 만들고 각 주어진 값을 치환 해보자.
function square(x){
return x*x;
}
function sumSquares(x,y){
return square(x) + square(y);
}
sumSquares(2,3); //13
sumSquares(2,3);
function sumSquares(2,3){ //각 x와 y의 자리에 2,3을 대입한다.
return square(2) + square(3);
}
function square(x){ //위의 sumSquares에 따라 x 자리에 2나 3이 대입된다.
return x*x;
}
위의 과정을 함수 적용의 치환 모형(substitution model 또는 대입 모형)이라고 부른다. 치환 모형은 함수 적용을 이해하기 위함이지 실제 해석기가 반드시 이런 식으로 동작하지는 않는다. 실제 해석기
들은 매개변수들에 대한 지역 환경
을 이용해서 치환을 처리한다.
인수 우선 평가 vs 정상 순서 평가
앞서 이야기한 평가 절차에 따르면 해석기는 함수와 인수 표현식을 먼저 평가하고 그 결과로 얻은 함수를 인수들에 적용한다. 그런데 이것이 평가를 수행하는 유일한 방법은 아니다.
- 정상 순서 평가 ( 또는 지연 평가 )
함수의 인수들이 실제로 필요한 시점에서 평가되는 방법 - 인수 우선 평가 ( 또는 적용적 순서 평가 )
먼저 인수들을 평가한 후 적용하는 평가 방법
자바스크립트 해석기가 실제로 사용하는 방법
인수 우선 평가는 같은 표현식이 여러번 평가되어서 생기는 비효율성을 피하는 것도 이유이긴 하지만, 치환 모형을 벗어난 함수들에 대해서는 정상 순서 평가가 훨씬 복잡하다는 점이 더 중요한 이유이다.
조건부 표현식과 술어
자바스크립트에서 조건부 표현식(conditional expression)을 이용하면 어떤 조건을 판정해서 그 결과에 따라 서로 다른 연산을 수행하는 사례 분석(case analysis)을 할 수 있다.
function abs(x){
return x >= 0 ? x : -x;
}
조건부 표현식을 평가할 때 해석기는 먼저 표현식의 술어(x≥0?)를 평가한다. 만일 술어가 참으로 평가되면 해석기는 귀결 표현식(x)을 평가해서 그 값을 조건부 표현식 전체의 값으로서 돌려준다. 만일 술어가 거짓이라면 대안 표현식(-x)를 평가해서 그 값을 조건부 표현식 전체의 값으로서 돌려준다.
수학 함수 vs 컴퓨터 함수
함수들은 통상적인 수학 함수와 아주 비슷하다. 수학의 함수처럼 컴퓨터의 함수도 하나 이상의 매개변수로 결정되는 하나의 값을 지칭한다. 하지만 수학 함수와 컴퓨터 함수에는 중요한 차이점이 있다. 바로 텀퓨터의 함수는 반드시 효과적이여야 한다는 점이다. 이것에 대한 예로 제곱근을 구하는 문제를 생각해보자
√x = y >= 0이고 y^2 = x라는 조건을 충족하는 y
이 정의를 통하여 제곱근을 알 수 있지만, 컴퓨터 함수를 서술하지는 않는다. 이 정의는 주어진 수의 제곱근을 실제로 구하는 방법에 관해서는 아무것도 말해주지 않는다.
수학 함수와 컴퓨터 함수의 이러한 차이점은 사물의 성질을 서술하는 것과 뭔가를 하는 방법을 서술하는 것의 차이를 반영한다. 이를 선언적 지식과 명력정 지식의 구분이라고 말하기도 한다. 수학에서는 주로 선언적 저술에 관심을 두지만 컴퓨터 과학에서는 주로 명령적 저술에 관심을 둔다.
예제 : 뉴턴 방법으로 제곱근 구하기
위의 공식을 사용하여 제곱근을 구하는 기능을 만들어보자. x의 제곱근이 될 만한 y 값을 추측하고, y와 x/y의 평균으로 더 나은 추측값을 구하는 과정을 반복하면 실제 제곱근에 점점 더 가까워진다.
/* 제곱근 함수 */
function sqrt(x){
//추측값은 1에서부터 시작한다.
return sqrt_iter(1,x);
}
function sqrt_iter(guess,x){
return is_good_enough(guess, x)?guess:sqrt_iter(improve(guess,x),x);
}
function improve(guess,x){
return average(guess,x/guess);
}
function is_good_enough(guess,x){
return abs(square(guess)-x)<0.001;
}
/* */
/* 수학 관련 함수 */
function abs(x){
return x >= 0 ? x : -x;
}
function square(x){
return x*x;
}
function average(x,y){
return (x+y)/2;
}
/* */
/* 제곱근 함수 실행 */
sqrt(9); // 3.00009155413138
/* */
참고 링크 : 뉴튼 법을 이용한 제곱근 구하기
뉴튼 법을 이용한 근사값 구하기 @ntalbs' stuff
블랙박스 추상으로서의 함수
위의 sqrt 함수는 하나의 계산적 과정을 서로 연관된 일단의 함수들로 정의하는 첫 번째 예이다. sqrt 프로그램은 함수들의 군집이라고 할 수 있다. is_good_enough 함수 관점에서 square는 하나의 구체적인 함수라기보다는 함수의 한 추상이라고 할 수 있다. 그러한 추상을 함수적 추상
(functional abstraction)이라고 부른다. 이러한 추상 수준에서 square는 제곱을 계산해서 돌려주기만 한다면 (구체적인 계산 방법이야 어떻든) 어떤 함수라도 상관 없다.
따라서 함수는 세부사항을 숨길 수 있어야 한다. 함수의 사용자는 함수가 어떻게 구현되었는지는 몰라도 함수를 사용할 수 있어야 한다.
지역 이름
함수의 사용자가 몰라도 되는 함수 구현의 세부사항 중 하나는 함수 작성자가 함수의 매개변수에 붙인 이름이다. 함수 매개변수 이름은 반드시 함수 본문 안에서만 유효한 지역 이름이여야 한다.
function square(x){
return x*x;
}
함수의 매개변수는 그 이름이 중요하지 않다. 그런 이름을 일컬어 함수 선언에 바인딩되었다 또는 묶였다(bound)라고 말한다. 주어진 이름의 바인딩이 유효하게 유지되는 문장들의 집합을 그 이름의 범위(scope)라고 부른다. 함수 선언에서 함수의 매개변수로 선언된 묶인 이름들의 범위는 함수의 본문 전체다. 위 함수에서 square는 묶이지 않은 자유로운 이름이지만 매개변수로 사용된 x는 묶인 이름이다.
내부 선언과 블록 구조
/* 제곱근 함수 */
function sqrt(x){
//추측값은 1에서부터 시작한다.
return sqrt_iter(1,x);
}
function sqrt_iter(guess,x){
return is_good_enough(guess, x)?guess:sqrt_iter(improve(guess,x),x);
}
function improve(guess,x){
return average(guess,x/guess);
}
function is_good_enough(guess,x){
return abs(square(guess)-x)<0.001;
}
/* */
/* 수학 관련 함수 */
function abs(x){
return x >= 0 ? x : -x;
}
function square(x){
return x*x;
}
function average(x,y){
return (x+y)/2;
}
/* */
/* 제곱근 함수 실행 */
sqrt(9); // 3.00009155413138
/* */
위의 sqrt 함수의 사용자에게 중요한것은 sqrt 함수 뿐이다. 다른 보조함수(sqrt_iter, is_good_enough, improve)는 사용자에게 혼란만 준다. is_good_enough 함수는 sqrt 함수에는 꼭 필요한 함수이며, 같은 이름의(is_good_enough 이름의) 다른 기능인 함수를 또 선언해선 안된다.
함수에서 중괄호 쌍은 하나의 블록(block)
을 이룬다. 블록 안에서 선언된 이름들은 그 블록 내부로만 한정된다. 그렇게 중첩된 구조를 블록 구조라고 부른다. 위의 보조함수를 sqrt 함수 블록 안으로 한정하는 것에서 그치지 않고, 더 단순하게 만들 수 있다. sqrt 함수의 매개변수로 x 값을 받고, 위의 보조함수들을 sqrt 함수 블록 내부에 넣는다. 보조함수들에서 사용되는 매개변수 x는 다시 전달할 필요가 없고 sqrt의 매개변수를 그대로 사용한다. 이러한 방법을 어휘순 범위 적용(lexical scoping)
이라고 부른다.
/* 제곱근 함수 */
function sqrt(x){
/* sqrt함수의 보조 함수 */
function is_good_enough(guess){
return abs(square(guess)-x)<0.001; //sqrt의 매개변수 x 사용
}
function improve(guess){
return average(guess,x/guess); //sqrt의 매개변수 x 사용
}
function sqrt_iter(guess){
return is_good_enough(guess)?guess:sqrt_iter(improve(guess)); //sqrt의 매개변수 x 사용
}
/* */
//추측값은 1에서부터 시작한다.
return sqrt_iter(1);
}
/* */
/* 수학 관련 함수 */
function abs(x){
return x >= 0 ? x : -x;
}
function square(x){
return x*x;
}
function average(x,y){
return (x+y)/2;
}
/* */
/* 제곱근 함수 실행 */
sqrt(9); // 3.00009155413138
/* */
'📝 꾸준함이 무기 > SICP' 카테고리의 다른 글
자바스크립트로 배우는 SICP (0) | 2023.02.08 |
---|