3. Overloading and Type Classes
많은 언어에서는 내장 데이터 타입을 특별히 취급한다.
예를 들어 C와 Java에서는 +로 float과 int를 더할 수 있지만, 서드파티 라이브러리의 임의 정밀도 수에는 사용할 수 없다.
마찬가지로 숫자 리터럴은 내장 타입에는 직접 사용할 수 있지만 사용자 정의 숫자 타입에는 사용할 수 없다.
다른 언어는 연산자에 대한 오버로딩 메커니즘을 제공하여, 같은 연산자에 새 타입을 위한 의미를 부여할 수 있게 한다.
이러한 언어(C++와 C# 등)에서는 다양한 내장 연산자를 오버로딩할 수 있으며, 컴파일러는 타입 검사기를 사용해 특정 구현을 선택한다.
숫자 리터럴과 연산자뿐 아니라 많은 언어에서 함수나 메서드도 오버로딩할 수 있다. C++, Java, C#, Kotlin에서는 인수의 개수와 타입이 서로 다른 메서드 구현을 여러 개 정의할 수 있다. 컴파일러는 인수의 개수와 타입을 사용해 어떤 오버로딩을 의도했는지 결정한다.
함수와 연산자 오버로딩에는 중요한 한계가 있다. 다형 함수는 특정 오버로딩이 존재하는 타입으로 타입 인수를 제한할 수 없다. 예를 들어 문자열, 바이트 배열, 파일 포인터에 대해 오버로딩된 메서드를 정의할 수 있지만, 이들 모두에서 동작하는 두 번째 메서드를 작성할 방법은 없다. 대신 이 두 번째 메서드 자체를 원래 메서드의 오버로딩이 있는 각 타입에 대해 오버로딩해야 하므로, 하나의 다형 정의 대신 많은 상용구 정의가 생긴다. 이 제한의 또 다른 결과는 일부 연산자(예: Java의 동등성)가 반드시 합리적이지 않은 경우에도 모든 인수 조합에 대해 정의된다는 것이다. 프로그래머가 주의하지 않으면 런타임에 충돌하거나 조용히 잘못된 결과를 계산하는 프로그램이 될 수 있다.
Lean은 Haskell에서 처음 도입된 타입 클래스라는 메커니즘으로 오버로딩을 구현한다. 이 메커니즘은 다형성과 잘 작동하는 방식으로 연산자, 함수, 리터럴을 오버로딩하게 한다.
타입 클래스는 오버로딩할 수 있는 연산의 모음을 설명한다.
새 타입에 대해 이러한 연산을 오버로딩하려면 새 타입의 각 연산 구현을 포함하는 인스턴스를 만든다.
예를 들어 Add 타입 클래스는 덧셈을 허용하는 타입을 설명하고, Nat의 Add 인스턴스는 Nat 덧셈 구현을 제공한다.
클래스와 인스턴스라는 용어는 객체 지향 언어에 익숙한 사람에게 혼란스러울 수 있다. 객체 지향 언어의 클래스·인스턴스와 밀접하게 관련되지 않기 때문이다. 그러나 공통된 뿌리는 있다. 일상 언어에서 “클래스”는 공통 속성을 공유하는 집단을 뜻한다. 객체 지향 프로그래밍의 클래스도 공통 속성을 가진 객체 집단을 설명하지만, 프로그래밍 언어에서 그런 집단을 설명하는 특정 메커니즘을 뜻하기도 한다. 타입 클래스 역시 공통 속성(즉 특정 연산의 구현)을 공유하는 타입을 설명하지만 객체 지향 프로그래밍의 클래스와 다른 공통점은 거의 없다.
Lean의 타입 클래스는 Java나 C#의 인터페이스에 훨씬 가깝다. 타입 클래스와 인터페이스는 모두 타입 또는 타입 모음에 구현되는 개념적으로 관련된 연산 집합을 설명한다. 마찬가지로 타입 클래스 인스턴스는 Java나 C# 클래스의 인스턴스라기보다 구현된 인터페이스가 요구하는 클래스 코드와 비슷하다. Java나 C# 인터페이스와 달리 타입 작성자가 접근할 수 없는 타입 클래스 인스턴스도 타입에 부여할 수 있다. 이 점에서 Rust의 트레이트와 매우 비슷하다.