프로그래밍 언어론 7-2-1강. 타입 동등성(Type Equivalence)

[프로그래밍 언어론]

2019. 12. 13. 18:43

 

[타입 동등성]

: 사용자가 새로운 타입을 정의하는 걸 허용하는 언어에서, 타입 동등 여부를 결정하는 주요 기준 두가지가 있다.

: 구조적 동등성 / 이름 동등성

 

 

[구조적 동등성 기준(Structual equivalence)]

: 타입 정의에 대한 내용 기반으로 동등성을 판단하는 기준

: 즉, 구조체상 같은형태일때 동등하다고 판단한다.

: Algol 68, Modula-3, C, ML등 에서 사용하는 방법

: 직관적이지만, low-level 실행 지향적이다.

 

# 구조적 동등성 예제

type student = record

  name, address : string

  age : integer

 

type school = record

  name, address : string

  age : integer

: name이 달라도 내부구조가 동일해 동일한 타입으로 취급한다 (위 student, school 동일한 타입취급) => 구조적 문제 발생 가능...

 

 

[이름 동등성 기준(Name equivalence)]

: 타입 정의가 발생한 맥락 기준으로 동등성을 판단

: 즉, 각 타입 정의할때마다 새로운 타입을 소개한다.

: Java, C#, Pascal, Ada 등에서 사용하는 방법

: 아무리 내부 구조가 동일하더라도 서로 다르게 두개의 타입을 선언한 시점에서 다른 타입으로 취급한다.

: 타입의 name이 동등성 판단의 기준.

 

 

 

[타입 동등성 기준에 따른 타입 동등성 판단 예시]

: 숙지해두기

type R1 = record

  a, b : integer

end;

: 위와 같은 타입이 있다. (Pascal)

 

# 타입 예시 1

type R2 = record

  a : integer;

  b : integer

end;

: 이와 같이 선언의 format만 다른 경우는 대부분 동일한 타입으로 판단한다.

 

# 타입 예시 2

type R3 = record

  b : integer;

  a : integer;

end;

: 이와 같이 선언의 순서가 다른 경우.

: ML에서는 동등하지 않다고 판단

: 대부분의 언어들은 동등하다고 판단

 

# 타입 예시 3

type str = array [1..10] of char;

type str = array [0..9] of char;

: 배열의 길이는 같지만 index값이 다른 경우

: 대부분의 언어들은 동등하지 않다고 판단

: 몇몇 Fortran, Ada같은 언어는 동등하다고 판단

 

 

 

[Name equivalence의 변형]

typedef old_type new_type;        // C언어

: new_type이 old_type에 대해 alias 된다.

: 그럼 이 두 타입을 2개의 이름을 가진 같은 타입(구조적 동등성)으로 취급? 아니면 동일한 구조를 가지고 있는 서로다른 두개의 타입(이름 동등성)으로 취급?

 

1) Strict name equivalence (염격한 이름 동등성)

  : 위와같은 방법으로 생성된 aliased type을 서로 다른 타입으로 취급하는 경우를 의미한다.

 

2) Loose name equivalence (느슨한 이름 동등성)

  : 위와 같은 방법으로 생성된 aliased type를 서로 같은 타입으로 취급한다 (두개의 name을 가진)

 

 

# Ada : 두 두가지 방법중 어느 방법을 사용할지 프로그래머에게 맡긴다.

  - subtype : 동등하게 취급하는 경우

  - derived type : 다르게 취급하는 경우 

 

 

 

[두가지 동등성 기준 대표적인 예시]**

type cell = .. -- whatever

type alink = pointer to cell       // alise

type blink = alink                   // alise

p, q = pointer to cell

r : alink

s : blink

t : pointer to cell

u : alink

# 엄격한 name equivalence 기준

  - p, q 는 동등한 타입

  - r, u 는 동등한 타입

  : 같은 타입 name을 이용해 생성한 것들만 동등하게 취급

 

# 느슨한 anme equivalence 기준

  - 위의 동등한 거 에 추가로

  - r, s, u 동등한 타입

  : name 달라도 alise 관계이기에 동등한 취급, 단 t는 다르다! (같은거 참조해도.. 이어져야함)

 

# 구조적 동등함 기준

  : 6개의 변수다 동일하다.

 

 

 

[타입 전환(Conversion)과 Cast]

: 정적 타이핑되는 언어에서 특정 타입의 값이 기대되는 많은 맥락들이 존재한다.

 

# 특정 값 기대 맥락 예시 1 - statement에서

a := expression     // 두 변수의 타입이 같다고 생각할 수 있다,

# 특정 값 기대 맥락 예시 2 - expression에서

a + b                // 이하동

# 특정 값 기대 맥락 예시 3 - subroutine에서

foo(arg1, arg2, ..., argN)   //인자들의 타입을 해당 subroutine의 선언시의 파라미터의 값과 동일할 것이라 생각한다.

 

: 이러한 기대를 위해 만약에 기대되는 타입과 들어가는 타입이 다르면 type conversion을 명시해줘야하고, 이는 실행 시점에 실행된다.

 

 

 

[타입 conversion 명시 주요 케이스]

1. 타입이 구조적 동일함(Structurally equivalent)로 고려되지만, 언어가 이름 동일성(name equivalence)으로 고려되는 경우

  : 그냥 쉽게 말해 구조는 같지만 이름이 다른 경우

  : low-level에서 값에 대한 동일한 세트를 가진다

  : 실행시점에 딱히 추가적으로 실행되지않고, 단순히 컴파일러가 눈감아 주면 문제 될 것이 없다.

 

2. 타입들이 서로다른 값들의 집합 가지지만, 교차 값이 같은 방식(부분집합)으로 나타날때. (대표적인경우)

  : 현재 변수가 유효한 기대타입 범위안에 있는지 실행시점에 검사한다 (check 해주는 코드 삽입)

  : 검사 실패시 -> dynamic semanitc error 발생

  : 검사 성공시 : 기존 값 변동없이 사용할 수 있다.

  # 케이스 2 예시

f(char)   // char의 범위는 -128 ~ 127

f(230)    // dynamic emantic error 발생

 

3. 타입이 서로다른 low-level 가지지만, 서로의 값들 사이에서 일부의 일치점을 정의하는 경우.

  : 이 경우에는 다른 타입으로 넘어갈때 서로의 타입 크기가 중요하다.

  1) 32비트 정수형 -> 실수형

    : 작은 범위의 타입에서 큰 범위의 타입으로 넘어가는 경우

    : 손실 없음

  2) 실수형 -> 정수형

    : 큰 범위 타입에서 작은 범위의 타입으로 넘어가는 경우

    : 손실 발생, overflow 발생

  3) 서로다른 길이의 정수형간 conversion

    : 높은데 위치해있는 바이트를 버리던가 부호 확장(sign-extending)한다.

 

# 타입 conversion 주요 예시

n : 정수형1

r : 실수형

t : 1...100 정수 subrange

c : 정수형2

...

t := n      // 3-2) 경우. run-tine sementic check 필요

n := t      // 3-1) 경우. 검사 X

r := n     // 2 경우. 실행 시점 conversion 필요

n := r     // 2 경우. 실행시점 conversion과 check 필요

c := n     // 1 경우.검사 필요 X

 

# C에서의 타입 conversion

r = (float) n;

n = (int) r;

: 실행 시점 conversion 수행

: 타입 conversion을 명시해 준다.

 

 

 

[Nonconverting Type Casts]*

: 변수의 타입 변환을 변수의 format을 바꾸는 것이아니라, 기존 값 그대로 두고 사용하는 방법이다.

: 그래서 더 작은 범위의 타입으로 변환된후 다시 큰 범위 타입으로 변환되어도, 값의 손실이 발생하지 않는다.

: 하지만, 위험한 상황 감지 어려워 진다

: 그래서 타입 시스템이 강한 언어에서는, 코드내 위험한 부분에 대해 적어도 label을 붙여, 문제 발생시 디버깅 수행한다.

 

# C++에서의 casting

: 기본적으로 C의 방식을 사용하는데, 추가로 더욱 안정적인 대안을 제공한다.

1) static_cast : 타입 conversion 수행. 기존 방식

2) reinterpret_cast : nonconverting type cast 수행

3) dynamic_cast : 다형성 타입의 포인터 방식 사용 (정적으로 유효성 보장 X, 동적으로 검사(checking))

A* a = (A*) p

// static_cast 같은 경우는 강제로 p를 A* 타입으로 형변환하는데, 이때 p가 A*대입 불가능시 문제가 발생한다.

// dynamic_cast가 이런 문제를 해결한다.