07. Data Types

Jimin Lim·2021년 12월 5일
0

Programming Language

목록 보기
6/9
post-thumbnail

07. Type Systems

  • Python: type 지정하지 않음
  • java, c: type 지정

→내부적으로 타입을 가지고 있다.

Purposes

  • 많은 연산자들을 위한 implicit context를 제공한다. → 명시하지 않아도 알 수 있게 해준다.
    • C언어의 경우, a+b 에서 두 변수가 정수 타입일 때, 정수 덧셈해준다.
    • double/float인 경우 floating-point 덧셈
    • Pascal 의 new p: block of storage를 heap에 생성, 위치 가리키도록 함 (pointer이므로 가능)
    • C++, Java, C#: new my_type() 공간할당 및 생성자를 호출할 수 있다.
  • 수행할 수 있는 작업에 제한을 걸 수 있다.
    • 구조체에 Character를 더한다? → 불가능
    • 정수를 인자로 받으려고 하는 함수에 file을 넘긴다? → 불가능 !
  • explicit type (C언어, Java): 읽거나 이해하는데에 쉽게 해준다.
  • 컴파일 시 타입을 알게 된다면, 최적화가 가능하다.
    • Basic: a=3 → 찐으로 타입이 없다, 무조건 가장 큰 double 로 만듦

7.1 Overview

  • bits 자체는 당연히 타입이 없다.
    • 하드웨어 상에서는 상관없다.
  • 어셈블리 또한 타입이 중요하지 않다.
  • high level의 경우 값에 타입을 연관짓는다.
  • 타입 시스템의 구성요소
    • 타입을 정의하고 이를 특정 언어 구조와 연결하는 메커니즘
    • type equivalence, type compatibility, type inference 존재
  • language constructs: 값을 가지는 것들, 값을 가지고 있는 것을 가리킬 수 있는 경우
  • 서브루틴은 타입으로 고려되는 경우가 있다.
    • first or second class 일 때 → 인자 전달 가능 first - 1. 변수 저장 2. 반환 3. 인자 전달 second - 3 가능, 1 or 2 third - 3 불가
    • 함수의 인자가 다 같을 때 함수가 같은 것을 파악할 수 있음
  • statically scoped language에서 서브루틴에 대한 레퍼런스를 동적으로 만들어 내지 않는 경우, 컴파일러는 함수 이름을 제대로 파악할 수 있음

Rules

  • Type equivalence :두 값의 타입이 같은 지
  • Type compatibility: 주어진 상황에 주어진 값이 사용될 수 있는지?
  • Type inference: 표현식을 구성하는 요소들, 주변 환경을 가지고 표현식 자체의 타입을 유추하는 것

→ 다형성의 경우, 상황에 따라 달라질 수 있으므로 타입을 구별하는 것은 중요하다.

Type checking

  • type clash: 규칙 위반
  • strongly typed: 타입을 잘못 사용하고 있을 때 checking 해줌
    • 1970년대 중반: strongly typed
      • c언어 점점 strongly typed → 중간에 아닌 경우 존재
      • non converting type casts: float a; ((int*) &a); (float → int)
      • union: int라 쓰고 float / pointer array 섞어쓰는 경우
      • c언어는 보통 compile 상 type checking
  • statically typed: strongly + compile
    • c언어, c++, java 포함 → 대부분의 타입체킹이 compile, 나머지는 runtime
  • late binding: 다형성 쓸 때 주로 사용함
    • 변수의 타입이 프로그램 중간에 변경될 수 있음
    • 대부분 스크립트 언어

7.1.1 The Meaning of "Type"

  • Denotational point of view: 집합에 속해있어야한다.
  • Structural point of view: built-in type, composite type
  • Abstraction-base point of view: 잘 정해지고, 일반적인 의미를 가지고 있는 operation들로 구성되어있는 interface ex) class, 모듈

7.1.2 Polymorphism

  • 다형성은 여러 개의 형태를 가지고 있는 타입
  • 공통적인 특성을 가지고 있어야하며 다른 특성에 의존해서는 안됨 Parent: 공통적인 특성 Parent p = new Child(); 의 경우 p에서 호출할 수 있는 함수는 Parent
  • Parametric polymorphism: generics
    • explicit: statically typed language 에 나타난다.
    • implicit: compile 에 만들어진다.
  • Subtype polymorphism: 상속 관계
    • static typing이랑 같이 일어날 경우, 대부분이 컴파일 때 일어남
    • 실질적으로 호출되는 함수는 runtime에 결정됨
  • Smalltalk, Python, Ruby: single mechanism → 특별히 구분하지 않고 run time상에서 타입체크

7.1.3 Orthogonality

  • higly orthogonal language 의 경우 쉽게 사용할 수 있고, 이해가 쉽다.
  • C, Algol68
    • statement, expression의 구별을 제거

    • procedure 형태로 사용할 수 있는 함수 제공, void로 모두 받을 수 있다.

    • 값 반환하는 함수를 사용하는 경우 있을 수 있음 (side effect 위해서) → 반환 값을 void 인 것처럼 타입 캐스팅해서 사용한다.

      → procedure 부르듯이 function을 부르지 못하는 경우를 막아서 orthogonality를 높게 해줌.

  • 변수 값을 지우는 경우
    • pointer types: null 반환
    • enumerations: none of the above
    • int, char 의 경우 무슨 값? → 빈 값을 각기 다른 값을 가지고 있다.
    • Optional 지원
    • 예시 - OCaml
      • None 을 통해서 어떤 타입이 들어와도 같은 값을 반환
  • Composite type의 초기화 (class, union, array)
    • c, java 에서는 초기화할 때 위처럼 사용가능 하지만 Ada는 값 변경시에도 사용 가능

7.1.4 Classification of Types

  • int, integer, double 등 타입에 따라서 명칭이 다르다.
  • 대부분 언어는 빌트인 타입을 제공한다
    • integers, characters, Booleans, real
  • Character: 아스키 1바이트 사용 → 최근은 유니코드 2바이트

Numeric Types

  • 기본적인 숫자 타입
  • c, fortan: 정수, 실수 구별해서 범위 지정하는 경우도 존재한다.
    • int < long < longlong
    • 하지만 이식성이 떨어진다.
  • C, C++, C#: signed/unsigned 구별
  • Fortan, C99, Common Lisp: 복소수 존재
  • discrete
    • integers, Booleans, characters
    • enumerations {MON, TUE}

Enumeration Types

  • 가독성 좋은 프로그램을 위해서 만들어짐
  • 원소들의 집합

Pascal

  • 순서 존재
  • int, enumeration 혼용해서 사용한다면 type crash 발생

C

  • c - 정수로 취급

  • 순서대로가 아님 (java 도 정수 지정 가능)

Ada

weekday'pos(mon) = 1
weekday'val(1) = mon

java

enum arm_special_regs { fp(7), sp(13), lr(14), pc(15);
    private final int register;
    arm_special_regs(int r) { register = r; }
    public int reg() { return register; }
}
...
int n = arm_special_regs.fp.reg();
  • 클래스로 사용, set, get 가능
  • Pascal, C의 경우 같은 스코프에서 두 개 이상 같은 이름의 enumeration type을 사용할 수 없다.
  • Java, C# 은 가능하다.
  • Ada: 컴파일러가 유추 가능할 경우 가능
  • C++
    enum class Days { sun, mon, tue, wed, thu, fri, sat };
    Days d = Days::sun;

Subrange Types

  • pascal에서 처음 사용
  • discrete base type 이어야 한다.

  • type: new 와 함께 사용, 섞어 쓸 수 없다.
  • subtype: weekday, workday섞어쓸 수 있다. (부분 집합의 개념)
  • 가독성이 좋은 프로그램, byte 타입이므로 최적화 가능

Composite Types

  • records, unions, arrays, sets, pointers, lists, files
  • records: field의 집합, cobol에 의해서 소개됨
  • unions: 공간 하나로 공유
  • arrays: 함수라고 생각해도 된다. index로 값 연결, characters의 집합을 string 취급하기도 한다.
  • sets: 같은 값을 중복해서 가질 수 없다.
  • pointers: l-values (int*: int를 가리키는 주소값), recursive data types에 주로 사용 (구조체에서 recursive하게 사용가능 하도록 해줌)
  • lists: 길이가 정해져 있지 않은 경우
  • files: 타입으로 들어가 있는 경우도 존재

7.2 Type Checking

  • Type compatibility: 특정 type의 object가 사용될 수 있는지
  • 2 + 3.2 : 타입이 다르지만 허용해준다.
  1. type conversion: 강제 형변환
  2. type coercion: 자동 형변환
  3. non-converting type casts: 시스템에서 주로 사용, 한 가지 종류의 타입에 대한 비트를 다른 값으로 변경 (메모리 두고 다르게 변환 / int → char)
  • 2+3 = 5과 같은 새로운 expression의 타입은?
    • type inference: 값을 유추한다.
    • pascal 의 경우 type casting 필요

7.2.1 Type Equivalence

: 새로운 타입 정의시 타입이 같다는 것을 어떻게 정의할 것인가?

  • Structural equivalence: 같은 컴포넌트로 구성되어 있을 때, 같다
  • Name equivalence: 코드가 어떻게 보여지는지, definition: 새로운 타입이다. (대부분 지원)

Structural equivalence

언어마다 정의가 조금씩 다르다.

Pascal

→ a, b 순서가 달라짐, ML에서는 다르다고 보고 대부분의 언어는 같다고 본다.


→ 대부분언어는 다르다고 보는데, 일부 언어는 compatible하다고 볼 수 있다.

  • 메모리 구조 측면에서 보면 더 직관적으로 볼 수 있다.

  • 프로그래머 입장에서는 다른 타입, 언어 입장에서는 같은 타입
  • 즉 마지막 줄은 에러가 안난다.

Name equivalence

두 개 따로 타입을 정의 → 구조가 같더라도 다르게 본다.

  • new_type: alias
  • 두 개를 같은 타입으로 볼 것인가? → alias 지만 다른 타입으로 봐야하는 경우가 있다.
  • strict name equivalence : alias 타입은 다른 타입이다
  • loose name equivalence : alias 타입은 같은 타입이다 (대부분 pascal)
  • Ada: strict/ loose 둘 다 지원 → derived: 새로운 타입, subtype: compatible

→ 타입을 정의할 때 기준, 즉 strict 인 경우에는 p, q 당연히 같고, r 과 u는 타입이 지정된 alink를 말하므로 같고 t는 다른 줄에서 타입을 정의하므로 다르다.

→ loose 의 경우, alias도 같다고 보므로, alink의 alias인 blink도 포함시키는 것.

  • strict name equivalence: p,q 같음 r,u 같음
  • loose name equivalence: r, s, u 같은 타입
  • structural equivalence: 다 같음

Equivalence Examples

  • C 언어: struct, union 은 nonequivalent types, 나머지는 Structurally equivalent
  • Java: class, interface 는 nonequivalent types, Structural → int, Arrays ⇒ Structurally equivalent

Type Conversion and Casts

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

a := expression : 두 변수의 타입이 같다고 예상

a+b : 일반적으로 같은 타입

foo(arg1, arg2, ..., argN) : formal parameters(함수에 정의된 인자) 과 인자들의 타입이 일치할 것이라고 예상

  • 예상과 제공한 타입이 완전히 일치하려면 강제형변환이 필요하다. (type cast: 강제형변환, coercion: 자동형변환)
  • conversion이 runtime 실행할 때, 특별한 코드가 실행되는 경우, 실행되지 않는 경우가 존재한다.
  1. 형변환을 해야하는 타입들이 structurally equivalent 하며 name equivalence 한 언어를 사용하는 경우

    = 메모리 구조는 같지만 이름이 다른 경우

    • 특별한 코드없이 run time 때 실행됨
  2. 타입들이 서로 다른 값을 가지고 있지만 교집합의 value가 같은 방식으로 나타날 경우

    ex) 0..20 10..30 : subrange 에서 겹치는 경우 존재

    • 유효한 범위에 있는지 runtime 상 check 해줘야한다.
    • 실패 → dynamic semantic error
    • 성공 → low level의 기존 값 변동없이 사용
    • 안전하지 않더라도 속도를 높이기 위해서 disable 가능
  3. 타입이 저장하는 방식이 다르지만 (int, float과 같이 low - level 자체가 다름), 서로의 값들 사이에서 일부의 일치점을 정의하는 경우

    • 32 비트 정수 → a floating point number 작은 범위 → 큰 범위이므로 손상 없다.
    • 실수 → 정수 큰 범위 → 작은 범위, overflow 발생 machine instruction 필요로 한다.
    • integer 종류가 다른 경우 (char, short, int) 8bit → 16bit 인 경우 확장한다. machine instruction 필요하는 것은 아니다.

Nonconverting Type Casts

: 변수의 format을 바꾸는 것이 아니라 있는 그대로를 사용하겠다는 것임

  • 값의 손실이 발생하지 않지만 위험한 상황인지 감지하기가 어려워진다.
  • float 데이터 포맷을 유지하면서 int 를 바꾸므로 위험하다. float 2.3f; 를 int 2;로 바꾸는 것처럼 데이터 포맷을 바꾸는 것이 아님.
  • ex) 구조체 → byte 단위로 처리하는 경우
  • c++ static_cast: 타입 conversion 수행, 기존 방식 (int) 처럼 사용 reinterpret_cast: nonconverting type cast 수행 float → int *((int*)&f) dynamic_cast: 다형성 타입의 포인터 방식 사용 Parent p; Child q = new Child*(); p = q; 가능 p를 child 로 바꾼다면? static을 사용한다면 p라는 것이 처음부터 child 인지 parent인지 알 수 없다. child* x = dynamic_cast<child*>p; : 바꿀 수 있는지 확인해줌, 바꿀 수 없다면 null이 들어간다.

7.2.2 Type Compatibility

: 사용할 수 있는지 없는지 따진다.

  1. Assignment statement: 호환 가능한 경우인지?
  2. operands of '+' 타입: +를 지원하는 일반적인 타입들이어야 한다.
  3. subroutine 호출: subroutine 에 전달되는 타입은 선언시 사용한 파라미터 타입과 호환되어야 한다.

→ 언어마다 호환성 범위에 따라서 허용 범위는 다르다.

Ada

  • 타입이 같을 때
  • S가 T의 subtype, 두 개가 같은 Parent
  • 같은 배열에서 같은 타입인 경우

Coercion

: 자동형 변환

  • 실행 시점에 수행되는 dynamic semantic 확인 코드가 필요하다
  • low-level에서 변경이 필요하므로 런타임상 변환 필요
short int s;
unsigned long int l;
char c;
float f;
double d;

s=l; //low-order bits가 signed 가 된다.

l=s; //첫번째 비트만 넘어가고 나머지는 0으로 채우고, 나머지는 숫자

s=c; //c는 sign-extended or zero-extended 된다.

f=l; //정수를 넣으면 f는 소수 부분 나누기때문에 날아갈 수도

d=f; //짧 -> 긴 문제 없음

f=d; //긴 -> 짧 손실 발생, 동적 시맨틱 체크는 하지 않으므로 오류 발생은 없다.
  • 프로그래밍할 때 정확한 명시를 하지 않아도 타입을 섞어쓸 수 있다는 장점은 있지만 type security를 낮춘다.

Universal Reference Types

: 어떤 타입이든 저장할 수 있는 컨테이너 타입

ex) Jave: Object, C/C++ : void *

  • void * p = q 인 경우, p의 값을 모른다. → 컴파일러는 object에 대한 어떤 연산을 허용하지 않는다. → 객체형변환 후 연산 가능
  • integer에 대해 reference 가지고 있는 것을 다른 타입의 reference로 재할당하는 것도 까다롭기 때문에 타입 safety가 필요하다.

Class Cast

객체지향언어에서, 왼쪽 객체가 오른쪽 객체 할당에 지원이 가능한지 유효성 검사 필요하다.

Student s = new Object(); (더 일반적 → 덜 일반적 )

→ 상속받은 Student 는 더 많은 기능이 존재할텐데, Object에 없는 내용을 지원할 것인가?

  • Tag 사용한다.
class A {
    public void print() {
		System.out.println("class A"); }
}
public class Main {
    public static void main(String[] args) {
				A a = new A(); 
        Object o = new String("hello");
				a = (A)o; //classCastException 
 } 
}
int main() {
  A* a;
	B* b = new B;
	C* c = new C;
	a = dynamic_cast<A*>(c); //NULL (c는 독립관계지만 우연히 같은 메소드 가짐)
	print(a);
	a = dynamic_cast<A*>(b); //valid (b가 a를 상속하고 있는 경우)
	print(a);
  delete c;
  delete b;
}
  • type tag 가 없다면 → type check 자체가 불가, runtime 시 확인할 방법이 없다. type conversion을 해줘야한다.

7.2.3 Type Inference

: 타입 추론, 전체적인 결과 타입은 뭐가 되는가? 2+2.3=?

  • 산술 연산 경우, 피연산자 타입을 따라간다. 피연산자 타입이 다를 경우, 다른 한 피연산자의 타입을 형변환한다.
  • 비교연산자의 결과는 boolean
  • 함수 호출의 결과는 header에 정의된 타입이다.
  • 결과는 왼쪽 type에 따른다.

Declarations

Ada

  • 반복문의 인덱스 변수는 for 내에 사용할 수 있도록 해준다.
  • ada에서는 max bound에 타입을 맞춰준다.
  • c는 for(int i = 0, ~) 처럼 타입 명시해준다.

Scala, C# 3.0, C++11, Go, and Swift

  • auto: 문맥에서 유추가능하게 해준다.

C++

  • decltype
  • 확장된 개념으로, 객체의 타입이 정해지지 않은 상태에서 임의의 결과의 타입을 사용하는 것이다.

7.3 Parametric Polymorphism

: 컴파일 시점에 타입 추론을 허용하지 않는 언어의 경우, run time에 실행해야 한다.

Scheme

(define min (lambda (a b) (if (< a b) a b)))
  • 타입이라는 개념이 없기때문에, 지원한다는 가정하에 실행시점까지 미룬다.
  • 지원하지 않는 타입이 들어간다면 런타임 에러 발생하도록 한다.

Smalltalk, Objective C, Swift, Python, Ruby

: 어떤 메소드가 호출되든간에 지원한다고 가정한다.

: 객체가 현재 사용된 메소드가 지원되는 메소드면 사용할 수 있다고 가정

duck typing: 객체가 요구되는 메소드를 지원할 때 수용가능한 타입을 가지고 있을 때

7.3.1 Generic Subroutines and Classes

: 다형성은 런타임 체크가 필요하다 → 시간이 많이 걸린다.

: 클래스 선언시에 타입을 명시해서 컴파일 시점에 검사할 수 있다.

(Java, C++, C# 등)

객체지향 언어에서의 일반화

: 클래스를 파라미터화 한다.

  • generics를 사용하지 않는다면 type cast가 필요하고, 컴파일 시점 체크가 힘들어진다.
  • 컴파일 시점에 T에 따른 코드를 만든다라고 생각해도 좋다.
  • 함수까지 따로 만들 필요 없으므로, 같은 set of argument에 대해서는 공유하긴 한다.
  • 같은 크기를 가지고 있는 int, float 또한 union처럼 공유할 수 있지 않을까?

Java

  • T를 Object로 바꿔서 사용한다.
  • 결국엔 Object 기반이다. → 컴파일러가 타입을 미리 알고 있으므로 Typecast를 알아서 해준다.

C#

  • C++ , Java 섞음
  • C++ 처럼 primitive or value types인 경우, 서로 다른 인스턴스를 만든다.
  • Java처럼 Class 타입이 들어간다면 하나의 코드를 가지고 사용한다.

Generic Parameter Constraints

: 일반화는 추상화이므로 모든 정보를 제공하는 것이 중요하다.

: 제네릭스 인자에 대해 제약을 거는 방식이 있다.

: 행해질 수 있는 operation들을 명확하게 처리할 수 있도록 해줘야한다.

Java, C#

  • 상속을 이용해서 제한을 걸 수 있다.
public static <T extends Comparable<T>> //여기서 보장해준다.
  void sort(T A[]) {
		...
    if (A[i].compareTo(A[j]) >= 0) 
		...
}

C++

  • 몇몇 언어는 제약 명시를 포기하지만 파라미터가 어떻게 사용되는지 검사한다.
    template<typename T>
    void sort(T A[], int A_size) { ...
    }
    • Comparable 있는 것을 구별하는 방법이 없다.
    • 컴파일러가 잘못되었다고 체크해준다.
    • 전달한 클래스가 구현되어있는 함수가 존재한다면? → 예상과 다른 결과가 나올 수 있다.
    • 프로그래머는 T라는 클래스에 대해서 abstract base class를 생성한다.

Implicit Instantiation

: 클래스는 타입이므로 사용하기 전 인스턴스를 생성해줘야 한다.

  • C++, Java, C#에서는 오버로딩의 개념이기때문에 따로 생성해주지 않는다.

Generics의 C++: 다양한 기능 구현해준다.

Java, C# : 다형성을 사용, 컨테이너 생성까지만 일반화 사용하도록 한다.

7.4 Equality Testing and Assignment

: 구조체, 리스트, 클래스의 경우 비교 같은 경우에 애매한 경우가 많다.

  1. aliase면 같다고 할 수 있는가?
  2. 문자열의 내용이 bit by bit로 같아야 하는가? → 문자열만 일치하면 되는가?
  3. 전체 10칸이 다 같아야하는가?
  4. 출력했을 때 같으면 같다고 하는건가?

→ (2)의 경우에 전체가 일치하다고 한다면 너무 low level이며 가비지 값이 들어간다면 fail한 경우가 많아진다.

String s1 = "hello";
String s2 = "hello";
String s3= new String("hello);

//s1 == s2 
//s1 != s3

Shallow comparison: 위치 비교만 한다, a= b;

Deep comparison: 실제 오브젝트가 같은지 확인한다, 복사본을 가리키도록 한다.

Value model 의 경우, 값을 복사한다. value가 pointer라면 shallow 인 경우이다.

→ 대부분 Shallow 를 사용한다.

//deep comparison 예시
class A { 
public:
    bool operator==(const A& c) {
				return (a == c.a && b == c.b); 
		}
    A& operator=(const A& c) {
        a = c.a;
				b = c.b; 
		}
private:
    int a;
		int b; 
}
profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글