Functional Programming in Lean

8.7. Special Types🔗

메모리에서 데이터가 표현되는 방식을 이해하는 것은 매우 중요하다. 보통 데이터 타입의 정의에서 표현 방식을 알 수 있다. 각 생성자는 태그와 참조 횟수를 포함한 헤더가 있는 메모리 객체에 대응한다. 생성자의 각 인자는 다른 객체를 가리키는 포인터로 표현된다. 즉 List는 실제로 연결 리스트이고 structure에서 필드를 꺼내는 일은 포인터를 따라가는 것이다.

그러나 이 규칙에는 중요한 예외가 있다. 컴파일러가 특별히 처리하는 타입이 몇 가지 있다. 예를 들어 UInt32 타입은 Fin (2 ^ 32)로 정의되지만 실행 시점에는 머신 워드 기반의 실제 네이티브 구현으로 대체된다. 마찬가지로 Nat의 정의는 List Unit과 비슷한 구현을 암시하지만, 실제 실행 표현은 충분히 작은 수에 즉시값 머신 워드를 사용하고 큰 수에는 효율적인 임의 정밀도 산술 라이브러리를 사용한다. Lean 컴파일러는 패턴 매칭을 사용하는 정의를 이 표현에 맞는 연산으로 변환하고 덧셈·뺄셈 같은 연산 호출을 기반 산술 라이브러리의 빠른 연산으로 매핑한다. 어쨌든 덧셈에 피연산자 크기에 선형인 시간이 걸려서는 안 된다.

일부 타입이 특별한 표현을 가진다는 사실은 이를 다룰 때 주의해야 함을 뜻한다. 이 타입 대부분은 컴파일러가 특별히 처리하는 structure로 이루어진다. 이런 구조체에서 생성자나 필드 접근자를 직접 사용하면 효율적인 표현과 증명에 사용하도록 설계된 표현 사이에 비용 큰 변환이 일어날 수 있다.

예를 들어 배열은 연결 리스트를 감싸는 구조체로 정의된다. 컴파일된 코드에서는 효율적인 동적 배열로 표현되며, 리스트에 생성자 Array.mk를 적용하면 선형 시간이 걸려 효율적인 배열로 바뀐다. 필드 접근자 toList는 배열을 연결 리스트로 되돌리며 이 역시 선형 시간이 걸린다. Array의 정의는 배열에 관한 증명을 쉽게 하기 위한 것이므로 실행할 코드에서는 이런 변환을 피해야 한다. 마찬가지로 String은 바이트 배열과 바이트가 유효한 UTF-8이라는 증명을 포함하는 구조체지만 실행 표현에는 문자열 문자 수를 캐시하는 필드가 추가로 들어 있다. 바이트 배열이 유효할 때만 생성자를 적용할 수 있으므로 실제 UTF-8인지 검사할 필요는 없지만 문자 수 계산에는 선형 시간이 걸린다. 접근자 toByteArray는 새 바이트 배열 객체를 할당하고 기반 바이트를 복사하므로 시간과 공간이 선형으로 든다. 문자열의 기본 연산 중 상당수는 컴파일러가 새 객체를 할당하는 대신 가능한 경우 실행 문자열을 변이하는 효율적인 버전으로 바꾼다.

타입 자체와 명제의 증명은 모두 컴파일된 코드에서 완전히 지워진다. 즉 공간을 차지하지 않으며 증명의 일부로 수행했을 계산도 마찬가지로 지워진다. 따라서 증명은 귀납적으로 정의된 리스트라는 배열의 편리한 인터페이스와 배열에 대한 귀납 증명을 활용하면서도 실행 중 느린 변환 단계를 추가하지 않는다. 이 내장 타입에서는 편리한 논리적 데이터 표현이 프로그램을 느리게 만든다는 뜻이 아니다.

구조체 타입에 타입도 아니고 증명도 아닌 필드가 하나만 있으면 실행 시점에 생성자 자체가 사라지고 그 하나의 인자로 대체된다. 즉 서브타입은 추가적인 간접 참조 층 없이 기반 타입과 동일하게 표현된다. 마찬가지로 Fin은 메모리에서 Nat과 같으며, 단일 필드 구조체를 만들어 성능 비용 없이 Nat이나 String의 서로 다른 용도를 추적할 수 있다. 생성자에 타입도 아니고 증명도 아닌 인자가 없다면 생성자도 사라지고 포인터가 사용될 자리에 상수값이 들어간다. 따라서 true, false, none은 힙 객체를 가리키는 포인터가 아니라 상수값이다.

다음 타입은 특별한 표현을 가진다:

타입

논리적 표현

실행 시점 표현

Nat

단항 표현. 각 Nat.succ에서 포인터 하나를 사용한다

효율적인 임의 정밀도 정수

Int

양수·음수 생성자를 가진 합 타입. 각각 Nat을 포함한다

효율적인 임의 정밀도 정수

BitVec w

적절한 상한 2^w를 가진 Fin

효율적인 임의 정밀도 정수

UInt8, UInt16, UInt32, UInt64, USize

올바른 폭의 비트 벡터

고정 정밀도 기계 정수

Int8, Int16, Int32, Int64, ISize

같은 폭으로 감싼 부호 없는 정수

고정 정밀도 기계 정수

Char

유효한 코드 포인트라는 증명과 쌍을 이룬 UInt32

일반 문자

String

toByteArray 필드에 ByteArray와 배열이 유효한 UTF-8이라는 증명을 포함하는 구조체

UTF-8 인코딩 문자열과 문자 수

Array α

toList 필드에 List α를 포함하는 구조체

α 값을 가리키는 포인터의 압축 배열

Sort u

타입

완전히 지워짐

명제의 증명

명제를 증명의 타입으로 볼 때 명제가 요구하는 데이터

완전히 지워짐

8.7.1. Exercise🔗

Pos의 정의는 Lean이 Nat을 효율적인 타입으로 컴파일하는 이점을 활용하지 않는다. 실행 시점에는 본질적으로 연결 리스트다. 또는 서브타입에서 설명한 것처럼 Lean의 빠른 Nat 타입을 내부적으로 사용하는 서브타입을 정의할 수 있다. 실행 시점에 증명은 지워진다. 결과 구조체에는 데이터 필드가 하나뿐이므로 그 필드 자체로 표현되며, 새 Pos 표현은 Nat 표현과 같다.

정리 {n k : Nat}, n 0 k 0 n + k 0을 증명한 뒤 새 Pos 표현의 ToStringAdd 인스턴스를 정의하라. 그런 다음 필요한 정리를 증명하며 Mul 인스턴스를 정의하라.