Control Abstraction and Data Types
#PLT
Control Abstraction
Control Abstraction은 프로그래밍 언어에서 데이터 추상화 (Data Abstraction)와 더불어 가장 중요한 개념 중 하나입니다. 복잡하고 큰 소프트웨어의 경우, 주요 목표를 달성하려면 더 작은 요구 사항을 충족시켜야 합니다. 이 개념은 분할 정복 (Divide and Conquer) 전략과 관련이 있습니다.
모바일 쇼핑 앱을 개발한다고 가정해 보겠습니다. 이 앱에는 다음과 같은 기능이 필요합니다:
- 상품 데이터 읽기
- 앱에서 상품 표시
- 상품 검색
- 고객 정보 관리
- 상품 리뷰
- 결제
- 구매 관리
- 배송 옵션 관리
이러한 모든 기능을 하나의 큰 프로그램으로 구현하는 것은 좋은 방법이 아닙니다. 대신, 각 기능을 독립적으로 제공하는 서브프로그램 (Subprogram)으로 구현할 수 있습니다. 이렇게 하면 다음과 같은 이점이 있습니다:
- 구현 세부 사항을 숨기고, 설계와 분리할 수 있습니다.
- 다른 구현으로 쉽게 교체할 수 있습니다.
- 각 서브프로그램이 어떻게 사용되는지만 알면 됩니다.
- 재사용할 수도 있다.
서브프로그램 (Subprogram)
서브프로그램은 프로시저 (Procedure) 또는 함수 (Function)라고도 합니다. 서브프로그램, 프로시저, 함수라는 용어는 이 강의에서는 서로 교환 가능하게 사용됩니다. 서브프로그램이 가장 일반적인 용어이지만, 이 강의에서는 더 친숙하고 후속 내용과 더 관련이 깊은 함수를 사용할 것입니다.
함수 (Function)는 다음과 같은 특성을 지닙니다:
- 이름으로 식별됩니다.
- 독립적인 로컬 환경 (Local Environment)을 가집니다.
- 매개변수 (Parameter), 반환값 (Return Value), 비지역 환경 (Non-Local Environment)을 사용하여 다른 코드와 정보를 교환합니다.
- 함수를 정의하거나 선언한 후 호출할 수 있습니다.
매개변수 전달 (Parameter Passing)
매개변수 전달은 서브프로그램이 프로그램의 다른 부분과 통신하는 중요한 방법입니다. 세 가지 매개변수 타입을 고려할 수 있습니다:
- IN 매개변수: 호출자(caller)에서 피호출자(callee)로의 통신
- OUT 매개변수: 피호출자에서 호출자로의 통신 (Return value와는 다르다)
- IN/OUT 매개변수: 양방향 통신
서브프로그램의 관점에서 Formal Parameters (Formal Parameters)와 Actual Parameters (Actual Parameters)가 존재합니다. Formal Parameters는 함수 선언에 나타나는 매개변수이고, Actual Parameters는 함수 호출 시 전달되는 매개변수(argument)입니다.
func(~ p1, ~p2) // Formal Parameters
func(x + y, 3) // actual parameters
Call by Value
이 방식은 IN 매개변수에 해당합니다. Actual Parameters는 expression이 될 수 있으며, 함수 호출 시 Actual Parameters가 평가되고 r-value가 Formal Parameters에 연관됩니다. 함수가 종료되면 Formal Parameters는 소멸하고 값도 함께 사라집니다. 값에 의한 호출(call by value)은 가장 단순하고 인기 있는 매개변수 전달 방식입니다. C와 Java에서 유일한 매개변수 전달 방식입니다.
Call by Reference (Call by Reference)
이 방식에서는 매개변수가 입력과 출력 모두에 사용될 수 있습니다 (IN/OUT 매개변수). Actual Parameters는 L-값 (L-value)이어야 하며, 함수 호출 시 Actual Parameters의 L-값이 Formal Parameters와 연관되어 별칭 (Aliasing)이 발생합니다.
값이 10이 된다.
자바는 call by reference가 없다. 변수 모델은 reference model이지만, parameter는 call by reference로 전달되지 않는다.
Call by Reference - Java의 예시
Java에서는 Call by Reference이 없지만, 참조 모델을 통해 객체의 속성 값을 변경할 수 있습니다. 하지만 두 변수를 실제로 교환하는 것은 불가능합니다.
Java 예시:
class A { int n; }
void swap(A a1, A a2) {
int tmp = a1.n;
a1.n = a2.n;
a2.n = tmp;
}
class A { int n; }
void swap(A a1, A a2) {
A tmp2 = a1;
a1 = a2;
a2 = tmp;
}
a1과 a2는 formal parameter라 function call 이후에는 x와 y는 그대로이다. call by value이기 때문.
값을 직접 바꿀 순 있지만, 참조를 바꿀 순 없다.
위 예시에서 a1.n
과 a2.n
의 값은 변경할 수 있지만, a1
과 a2
자체를 교환하는 것은 불가능합니다.
상수에 의한 호출 (Call by Constant)
함수 body에서 매개변수가 수정되지 않는 경우, Call by Value의 의미를 유지하면서 Call by Reference로 구현할 수 있습니다. 이때 매개변수는 읽기 전용 (Read-Only)으로 간주됩니다.
C++ 예시:
void func(const MyObject& obj);
이 방식은 값을 변경하지 않으면서 효율적으로 참조를 통해 매개변수를 전달하는 방법입니다.
레퍼런스로 전달하지만 값을 바꿀 순 없다~
Call by Result
Call by Result은 출력 전용 (Output-Only) 통신을 구현하는 방식입니다. Actual Parameters는 l-값 (L-value)이어야 하며, 함수 종료 후 Formal Parameters의 값이 Actual Parameters에 복사됩니다. 이를 후방 할당 (Backward Assignment)이라고 합니다.
C 예시:
void foo(res int x) { x = 5; }
int y = 2;
foo(y); // 함수 호출 후 y는 5가 됩니다.
함수가 실행될 때는 y와 아무 상관이 없다. 함수가 끝난 뒤에 copy되는 것이다.
이 방식은 다수의 결과를 반환해야 하는 함수 설계를 간소화할 수 있습니다. 예를 들어, 여러 매개변수를 반환하는 함수에서는 각 매개변수의 순서를 신경 써야 합니다.
Call by Value-Result
값에 의한 호출 (Call by Value)과 Call by Result (Call by Result)을 결합한 방식입니다. 양방향 통신을 구현하며, Actual Parameters는 l-값 (L-value)이어야 합니다.
함수 호출 시, Actual Parameters의 r-값이 Formal Parameters에 할당되고, 함수가 종료될 때 Formal Parameters의 값이 Actual Parameters에 복사됩니다.
예시:
void foo(val-res int x) {
x = x + 1; // 2 + 1 = 3
}
int y = 2;
foo(y); // 함수 호출 후 y에 x 값이 복사되어 y 값은 3이 됩니다.
Call by value-Result 예시
값-결과 방식에서는 같은 매개변수를 두 번 전달할 때 주의해야 합니다.
Call by value-Result:
void foo(val-res int x, val-res int y) {
x = 1;
y = 3;
if (x == y) // false
y = 1;
}
int a = 2;
foo(a, a); // 호출 후 a는 3이 됩니다.
Call by Reference:
void foo(ref int x, ref int y) { // 같은 메모리 주소를 가리킨다
x = 1;
y = 3;
if (x == y) // true - aliasing
y = 1;
}
int a = 2;
foo(a, a); // 호출 후 a는 1이 됩니다.
Call by Name
Call by Name은 현대 프로그래밍 언어에서 사용되지 않는 방식이지만, 개념적으로 중요한 의미를 가집니다. 이 방식은 함수 body에서 Formal Parameters를 Actual Parameters의 이름으로 대체하는 방식입니다.
예시:
void foo(name int x) {
x = 1;
}
int y = 2;
foo(y); // foo 함수는 void foo(int y)로 변환되어 y = 1이 됩니다.
Call by Name의 문제점
단순히 매개변수 이름을 대체하는 것만으로는 문제가 발생할 수 있습니다.
예시:
int x = 0;
int foo(name int y) {
int z = 2;
return z + y; // z + x + 1이고 x = 0, z = 2이므로 3
}
int a = foo(x + 1); // a = z + x + 1 = 3
또 다른 예시:
int x = 0;
int foo(name int y) { y 가 x + 1이 된다.
int x = 2;
return x + y; // x + x + 1
}
int a = foo(x + 1); // a = x + x + 1 = 5
두 코드는 local 변수 이름만 바꿨고, 동작은 같다. 하지만 결과는 다르다.
따라서 Actual Parameters(actual parameters)와 그 평가 환경 (Evaluation Environment)을 함께 전달해야 한다.
고차 함수 (Higher-Order Functions)
고차 함수란 함수를 매개변수로 받거나 함수 자체를 반환하는 함수를 의미합니다. 이 메커니즘은 특히 함수형 언어에서 많이 지원됩니다.
함수의 매개변수로서의 사용 (Functions as Parameters)
아래는 C 언어에서 함수를 매개변수로 사용하는 예입니다. 함수 f
는 함수 g
의 매개변수로 전달됩니다.
int x = 1;
int f(int y) {
return x+y;
}
int g(function<int(int)> h) {
int x = 2;
return h(3)+x;
}
int main(){
//Functions as parameters
int x = 4;
int z = g(f);
변수 x
는 여러 번 정의될 수 있으며, g
함수 안에서는 x
에 대한 어느 바인딩(binding)이 사용되어야 하는지 결정해야 합니다. 또한, 어떤 환경에서 변수 x
를 확인할지도 중요합니다.
위 예시에서 마지막 f 함수에서 x는 어떤 x를 사용해야 할까?
깊은 결합과 얕은 결합 (Deep and Shallow Binding)
- 깊은 결합 (Deep Binding): 함수와 관련된 환경이 함수와 연결된 시점에서의 환경을 사용합니다.
- 얕은 결합 (Shallow Binding): 함수가 호출되는 시점에서의 환경을 사용합니다.
깊은 결합과 얕은 결합 (Deep and Shallow Binding)** (계속)
예시:
- 정적 스코프 (Static Scope)와 깊은 결합 (Deep Binding), 얕은 결합 (Shallow Binding)의 결과:
h(3) = 4
,g(f) = 6
- 동적 스코프 (Dynamic Scope)와 깊은 결합 (Deep Binding)의 결과:
h(3) = 7
,g(f) = 9
- 동적 스코프 (Dynamic Scope)와 얕은 결합 (Shallow Binding)의 결과:
h(3) = 5
,g(f) = 7
정적 스코프, 동적 스코프에 대해 이해하고 있으면 Deep Binding과 Shallow Binding만 구분해주면 된다.
Deep은 actual parameter가 전달되는 env를 사용한다. shallow는 호출된 곳의 env를 사용한다.
변수 x
의 값이 함수 호출 시점에 어떤 환경에서 결정되는지가 결과에 큰 영향을 미칩니다.
환경을 정의하는 요소 (What Defines the Environment?)
환경을 정의하는 요소는 다음과 같습니다:
- 가시성 규칙 (Visibility Rules): 어떤 범위에서 변수를 사용할 수 있는지를 결정합니다.
- 예외 사항 (Exceptions in Visibility Rules): 재정의된 이름이나 선언 이후에 사용할 수 있는 이름을 고려해야 합니다.
- 스코프 규칙 (Scope Rules): 변수의 범위와 사용 가능 시점을 결정합니다.
- 매개변수 전달 방식 (Parameter Passing Modes): 매개변수가 전달될 때 그 값이나 참조가 어떻게 처리될지를 결정합니다.
- 결합 정책 (Binding Policy): 함수나 변수의 이름이 언제, 어디서 어떤 값과 결합될지를 결정합니다.
함수를 반환값으로 사용 (Functions as Results)
함수는 또 다른 함수를 반환할 수 있습니다. 정적 스코프에서는 반환된 함수가 호출될 때, 그 함수의 환경도 함께 유지되며, 이는 클로저 (Closure)로 처리됩니다.
예시:
int x = 1;
int s() {
return x+1;
}
function<int()> calling_s {
return s;
}
int main(){
//Functions as results
int x = 4;
function<int()> k= calling_s();
int y = k();
k를 호출하면 결국 s를 호출하게 된다. 이 때 s에서 x = 1이다. 따라서 return value는 2가 된다.
클로저 (Closure)
클로저는 (expression, 환경)의 쌍으로, 환경에는 그 expression에서 사용된 자유 변수들이 포함됩니다. 자유 변수 (Free Variables)란 해당 expression에서 사용되었지만, 현재 환경에서 선언되지 않은 변수들입니다.
예를 들어, Python에서는 전역 변수가 자유 변수로 간주되지 않습니다. 하지만 대부분의 언어에서는 자유 변수를 환경에 포함합니다.
데이터 타입 (Data Types)
데이터 타입 (Data Type)이란 동질적인 값들의 집합과 그 값을 조작할 수 있는 연산들의 집합을 의미합니다.
- 동질적 (Homogeneous): 동일하거나 유사한 종류의 값들이 모인 집합입니다.
- 값과 연산 (Values + Operations): 데이터 타입은 값뿐만 아니라 그 값에 적용할 수 있는 연산도 포함합니다.
- oop의 클래스
예시:
- 정수형, 문자열형, 배열형 등은 각기 다른 연산을 필요로 합니다.
Type System
프로그래밍 언어는 고유의 Type System (Type System)을 가지고 있으며, 이는 데이터 타입을 관리하기 위한 규칙과 정보를 포함합니다.
Type System은 보통 다음과 같은 요소들로 구성됩니다:
- 미리 정의된 타입 집합 (Predefined Types): 언어가 제공하는 기본 데이터 타입들입니다.
- int, float, …
- 새로운 타입을 정의할 수 있는 메커니즘 (Mechanisms for Defining New Types): 사용자가 새로운 데이터 타입을 정의할 수 있도록 지원하는 메커니즘입니다.
- typedef
- 타입의 동등성, 호환성, 추론 규칙 (Equivalence, Compatibility, and Inference Rules): 타입 간의 관계를 정의하고 관리하는 규칙입니다.
표현 가능한 값 (Denotable, Expressible, Storable)
값은 다음 세 가지 기준으로 분류될 수 있습니다:
- 이름을 가질 수 있는 값 (Denotable Values): 변수 이름 또는 함수 이름과 같이 이름으로 참조될 수 있는 값.
- 표현 가능한 값 (Expressible Values): 복합 expression으로부터 얻을 수 있는 값.
- 예: 숫자, 문자열, 또는 메모리 위치 (C 언어에서 expression에서 나타날 수 있는 값).
- 위 예시에서 calling_s()에서 s를 얻었다.
- 저장 가능한 값 (Storable or Updatable Values): 변수를 통해 저장하거나 업데이트할 수 있는 값.
Variable vs Function : 비록 함수 코드가 디스크에 저장될 수 있지만, 프로그램 내에서 함수의 코드를 수정하거나 업데이트하는 것은 불가능하다.
Static Type Checking와 Dynamic Type Checking (Static and Dynamic Type Checking)
- Dynamic Type Checking (Dynamic Type Checking): 프로그램이 실행되는 동안 타입 제약을 검사합니다.
- Static Type Checking (Static Type Checking): 프로그램이 컴파일되는 동안 타입 제약을 검사합니다. Static Type Checking는 런타임 타입 검사가 없으므로 프로그램의 실행이 더 효율적일 수 있습니다.
Static Type Checking는 설계가 더 복잡하고, 컴파일 시간이 오래 걸릴 수 있습니다. 하지만 컴파일은 프로그램을 한 번만 실행하고, 프로그램 실행은 여러 번 수행되므로, 프로그램의 전체적인 성능에 더 유리할 수 있습니다.
Static Type Checking는 종종 보수적인(conservative) 타입 제약을 요구합니다. 이러한 이유로, 런타임에 실제로 발생하지 않을 오류까지도 보고할 수 있습니다.
예시 코드:
int x = 0;
if (x > 0)
x = "PL"; // 타입 오류 발생
x = 1 + 2;
위 코드에서 x = "PL"
은 정수형 변수인 x
에 문자열을 할당하려고 시도하기 때문에 타입 제약을 위반하는 오류가 발생합니다. 하지만 이 코드는 런타임 중에는 이 조건이 충족되지 않으므로 실제로 실행되지 않습니다. 이처럼 프로그램이 런타임에 타입 오류를 일으킬지를 미리 결정하는 것은 결정 불가능 (Undecidable)합니다.
타입 검사 필요성 (Necessity of Combination)
대부분의 고급 프로그래밍 언어들은 Static Type Checking와 Dynamic Type Checking를 모두 사용합니다. 예를 들어, 배열의 크기가 동적으로 결정되는 경우 배열 인덱스의 경계를 확인하는 것은 런타임에 수행됩니다. 이는 정적 검사로 처리할 수 없기 때문에 동적 검사로 처리합니다.
스칼라와 복합 타입 (Scalar and Composite Types)
- 스칼라 타입 (Scalar Type): 여러 값의 집합이 아닌 단일 값으로 정의되는 타입.
예: 불리언, 문자, 정수, 실수, 열거형.
열거형 예시:type days = {Mon, Tue, Wed, Thu, Fri, Sat, Sun};
- 복합 타입 (Composite Type): 여러 값을 포함하는 데이터 타입. 스칼라 타입이 아닌 모든 타입.
예: 구조체 (Record), 배열 (Array), 집합 (Set), 포인터 (Pointer), 함수 (Function), 재귀 타입 (Recursive Types). 이 타입들은 다른 operations 들을 가지고 있다.
타입 동등성 (Type Equivalence)
타입 동등성에는 다음과 같은 세 가지 주요 규칙이 있습니다:
- 이름 동등성 (Name Equivalence): 두 타입이 이름이 같으면 동일한 타입으로 간주합니다.
- 구조 동등성 (Structural Equivalence): 두 타입의 구조가 같으면 동일한 타입으로 간주합니다.
- 선언 동등성 (Declaration Equivalence): 두 타입이 함께 선언되었거나 단순히 이름만 다르게 정의되었으면 동일한 타입으로 간주합니다.
- TYPE1 = TYPE2라면 이에 해당
참조 투명성 (Referential Transparency): 두 동등한 타입은 서로 대체될 수 있으며, 프로그램의 의미를 바꾸지 않습니다.
현대 언어들은 예외와 함께 이 중 한가지 규칙을 사용하고 있다.
이름 동등성 (Name Equivalence)
이름 동등성 규칙은 매우 엄격한 규칙으로, 두 타입의 이름이 다르면 서로 다른 타입으로 간주됩니다.
type <type_name> = <expression>;
예시:
type Type1 = int;
type Type2 = int;
type Type3 = 1..100;
type Type4 = 1..100;
위의 경우, Type1과 Type2, Type3과 Type4는 이름이 다르기 때문에 서로 다른 타입으로 간주됩니다. Java와 C++는 대부분의 타입에서 이름 동등성을 사용합니다.
구조 동등성 (Structural Equivalence)
구조 동등성은 두 타입의 구조가 같으면 동일한 타입으로 간주하는 더 느슨한 규칙입니다.
예시:
type Type1 = int;
type Type2 = int;
type Type3 = 1..100;
type Type4 = 1..100;
Type1과 Type2는 동일한 구조를 가지므로 동일한 타입으로 간주되고, Type3과 Type4도 동일한 타입으로 간주됩니다. Java arryas나 C arrays 그리고 typedef가 이에 해당
구조적 동등성 (Structural Equivalence)
구조적 동등성은 두 타입의 구조가 동일할 때 동등하다고 간주하는 방식입니다. 이 방식은 더 느슨한 규칙으로, 타입의 이름보다는 구조를 중시합니다.
type Type1 = struct { int a; int b; };
type Type2 = struct { int n; int m; };
위의 두 타입은 필드 이름은 다르지만, 구조가 동일하기 때문에 구조적 동등성에 따르면 두 타입은 동등합니다. 하지만, 일부 언어에서는 필드 이름이 다를 경우 이들을 다른 타입으로 간주하기도 합니다.
구조 동등성 - 재귀 타입 (Structural Equivalence with Recursive Types)
재귀 타입의 경우, 구조 동등성으로도 동등성을 판단할 수 없습니다. 재귀적으로 정의된 구조체는 타입 검사로는 동등성을 해결할 수 없으므로, 일반적으로 재귀 타입은 동등하지 않다고 간주됩니다.
예시:
type Type1 = struct {
int a;
Type2 b;
};
type Type2 = struct {
int a;
Type1 b;
};
선언 동등성 (Declaration Equivalence)
선언 동등성은 이름과 구조 동등성의 중간에 위치하는 규칙입니다. 두 타입이 함께 선언되었거나 단순히 이름만 변경되었다면, 동일한 타입으로 간주합니다.
예시:
type Type1 = int;
type Type2 = Type1;
type Type3 = 1..100;
type Type4 = 1..100;
위의 경우, Type1과 Type2는 동일한 타입으로 간주되지만, Type3과 Type4는 서로 다른 타입입니다.
43페이지: 타입 호환성 (Type Compatibility)
타입 T가 타입 S와 호환된다고 할 수 있는 경우는 다음과 같습니다:
- 동등한 타입 (Equivalent Types): 타입 T와 S가 동일한 경우. 즉, 두 타입이 동등성을 만족할 때, T는 S와 호환됩니다. 예를 들어, 참조 투명성(Referential Transparency)을 만족하는 경우가 이에 해당합니다.
- T의 값이 S의 값의 부분집합인 경우 (T's Values are a Subset of S's Values): 타입 T의 값들이 타입 S의 값들의 부분집합일 때 두 타입이 호환됩니다. 예를 들어, 구간
1..10
과1..100
사이의 관계를 생각할 수 있습니다. - S의 모든 연산이 T에 적용될 수 있는 경우 (All the Operations of S can be Applied to T):
예를 들어, 다음과 같은 구조체 S와 T가 있다고 가정합시다.
S는 필드 a에만 접근할 수 있는 연산을 허용하며, T에서도 동일하게 a에 대한 연산을 적용할 수 있습니다. 이는 S의 연산을 T에서도 사용할 수 있음을 의미합니다.type S = struct { int a; }; type T = struct { int a; char b; };
- T의 값이 S의 값과 일대일 대응이 가능할 때 (T's Values Correspond to S's Values in a Canonical Fashion):
예를 들어, 정수형int
와 실수형float
이 대응될 수 있는 경우가 있습니다. T는 S의 부분집합이 아니지만, 정수형 값을 실수형으로 사용할 수 있습니다 (예:2
를2.0
으로 변환).
- T의 값이 변환을 통해 S의 값으로 변환될 수 있을 때 (T's Values can be Converted to S's Values with Transformation):
예를 들어, 실수형float
을 정수형int
로 변환할 수 있습니다. C 언어에서는 내림 변환(rounding)을 통해 실수를 정수로 변환합니다. (e.g rounding down in C)
타입 변환 (Type Conversion)
- 암시적 변환 (Implicit Conversion, Coercion): 프로그래머가 명시하지 않아도 컴파일러에 의해 자동으로 타입 변환이 일어나는 경우.
- T와 S가 compatible할 때, conversion은 프로그래머가 명시하지 않아도 자동으로 수행된다
- ex) int x = 2; float y = x;
- 명시적 변환 (Explicit Conversion, Cast): 프로그래머가 직접 타입 변환을 명시하는 경우.
예시:
S s = (S)t; // 명시적 타입 변환
업캐스팅과 다운캐스팅 (Upcasting vs. Downcasting)
클래스 간의 상속 관계에서 타입 변환이 가능할 수 있습니다.
- 업캐스팅 (Upcasting): 자식 클래스를 부모 클래스로 변환하는 것. 암시적 변환이 가능합니다.
예:Student s = new CSEStudent();
- 다운캐스팅 (Downcasting): 부모 클래스를 자식 클래스로 변환하는 것. 명시적 변환이 필요합니다.
예:CSEStudent c = (CSEStudent) s;
업캐스팅은 부모 클래스가 자식 클래스보다 더 일반적이기 때문에 변환이 자동으로 이루어지며, 다운캐스팅은 자식 클래스의 세부사항을 다루기 때문에 명시적으로 변환해야 합니다.
타입 검사와 타입 추론 (Type Checking and Inference)
- 타입 검사 (Type Checking): expression E와 타입 T가 주어졌을 때, E가 T 타입인지 확인하는 과정입니다.
예:
여기서int f(int a) { return a + 1; }
a + 1
은 반드시 정수형이어야 합니다. - 타입 추론 (Type Inference): expression E만 주어졌을 때, E의 타입을 유추하는 과정입니다.
예:
여기서 1은 정수이고,def f(a): return a + 1
+
연산자는 두 정수를 더해야 하므로,a
는 정수형이어야 하며, 함수f()
는int -> int
타입입니다.
타입 안전성 (Type Safety)
타입 검사와 타입 추론의 목적은 언어의 타입 안전성 (Type Safety)을 확보하는 데 있습니다. 타입 시스템 (Type System)이나 프로그래밍 언어는 프로그램이 언어에서 정의된 타입의 구분을 위반하지 않도록 보장할 때 타입 안전(type safe)하다고 말합니다.
- 안전하지 않은 언어 (Unsafe Languages): 메모리에 직접 접근할 수 있는 포인터를 사용하는 언어 (예: C, C++)는 메모리 안전성 문제가 발생할 수 있습니다.
- 부분적으로 안전한 언어 (Locally Safe Languages): Pascal과 같은 언어는 일부 안전하지 않은 부분을 포함할 수 있습니다.
- 안전한 언어 (Safe Languages): Scheme, ML, Java와 같은 이론적으로 숨겨진 type 오류를 생성하지 않는 언어들입니다.
요약 (Summary)
다음은 이 장에서 다룬 주요 개념들입니다:
- 제어 추상화 (Control Abstraction)
- 서브프로그램 (Subprograms)
- 매개변수 전달 (Parameter Passing)
- 결합 정책 (Binding Policy)
- 고차 함수 (Higher-Order Functions)
- 데이터 타입 (Data Types)
- 타입 동등성 및 호환성 (Type Equivalence and Compatibility)
- 타입 검사, 타입 추론 및 타입 안전성 (Type Checking, Inference and Safety)
'Programming Language > Theory' 카테고리의 다른 글
[PLT/프로그래밍언어론] 09. PL Paradigms, Scripting Language (0) | 2024.12.22 |
---|---|
[PLT/프로그래밍언어론] 08. 중간 정리 (0) | 2024.12.22 |
[PLT/프로그래밍언어론] 06. Control Structure (0) | 2024.12.21 |
[PLT/프로그래밍언어론] 05. Memory Management (0) | 2024.11.20 |
[PLT/프로그래밍언어론] 04. Names, Bindings and Scopes (0) | 2024.11.19 |