함수형 프로그래밍 언어에서는 Exception과 Null을 사용하지 않는다. 대신, Result(또는 Try)와 Option(또는 Optional)을 사용한다.

다음과 같은 자바 함수가 있다고 하자:

int getIndexOf(int idx) {
		if (idx < 0) throw new Index­Out­Of­Bounds­Exception()
;
		if (globalIntList == null) throw new Null­Pointer­Exception();

		if (globalIntList.size() <= idx) throw new Exception();

		return globalIntList[idx];
}

위의 함수는 기본적으로 int 타입을 리턴한다. 그러나, 실제 함수의 작동을 보면 IndexOutOfBoundsException, NullPointerException, Exceptionint를 리턴한다. 실제 리턴되는 타입과 함수에 표기된 리턴 타입이 불일치한다. 그러므로 int, IndexOutOfBoundsException, NullPointerException, Exception 이 동시에 리턴됨을 표시해 주어야 한다. 이것을 표현할 수 있는 방법이 대수적 타입의 합 타입(Sum Type)이다.

대수적 타입

우선 타입에 대해 생각해 봐야 한다. 타입은 값의 집합으로 생각할 수 있다. 예를 들어, Byte 타입은 -128, -127, -126, -125 …. 125, 126, 127 가 모인 집합이다. String 타입은 “abcd” , “가나다라" 등등 모든 표현할 수 있는 문자열에 대한 집합이다. Any 또는 Object 타입 아래에는 String 타입의 값이 들어갈 수 있다. Any 타입은 “모든 있을 수 있는 값”의 집합으로 생각할 수 있고, Object 타입은 모든 “객체”의 집합으로 생각 할 수 있다.

타입을 집합으로 보기 시작하면 공집합, 교집합과 합집합을 생각할 수 있다. 공집합은 주로 Never 타입으로 불린다. 합집합은 위에서 본 것 처럼 -128, -127, -126, -125 …. 125, 126, 127 와 같이 값들이 합쳐진 Byte 타입이나, Java의 Object 타입을 생각할 수 있다. Super Class이기 때문이 아니라, Object | Null 의 두 개 값이 합쳐져 있기 때문이다. 교집합은 조금 생소 힐 수 있다. 아래와 같이 A타입, B타입, C타입이 동시에 적용되는 타입을 잡을떄 교집합을 생각할 수 있다.

public static <T extends LogReader & LogStreamer> void streamLog(T logSystem){
    logSystem.read().forEach(logSystem::stream);
}

<aside> 💡 엄밀하게 말하면 Unit, Void는 어떤 값을 가진다

</aside>

대수적 타입에서는 모든 타입을 타입의 (Sum) 또는 (Product)으로 볼 수 있다고 생각한다. 예를들어 Byte (1Byte = -128~127) 타입을 생각해 보자. 값 중 하나로 생각할 수 있다.

<aside> 💡 타입의 최소 단위는 값 자체가 될 수 있다. 예를 들어서 String a = "가나다라" 라는 Java 코드가 있다고 하자. 이 값의 가장 큰 타입 범위는 Object가 된다. 그리고 Serializable, Comparable<String>, CharSequence 수준의 타입을 거쳐서, 우리가 자주 사용하는 String 타입이 된다. 그러나 “가나다라” 자체도 타입이 될 수 있다.

</aside>

그렇기 때문에, 처음 나온 함수 자체를 보면 다음과 같은 리턴 타입으로 이해할 수 있다: int | IndexOutOfBoundsException | NullPointerException | Exception 이것을 타입의 합으로 생각할 수 있다. 다르게 생각하자면 리턴 타입은 총 4개의 값을 가질 수 있다. (1 + 1 + 1 + 1 = 4, 쉬운 설명을 위해서 Exception 내의 String등은 잠시 치웠다.)

만약 Rust 스타일로 합 타입을 표기한다면 다음과 같을 것이다:

enum ReturnType {
	Int(i32),
	IndexOfOutBoundsException,
	NullPointerException,
	Exception
}

그러면 곱은 어떻게 될까? 아래 코드를 보자. 아래의 Person 타입은 Int * Byte * Int 의 값을 표현할 수 있다. 즉, 해당 타입은 Int의 값 수 * Byte의 값 수 * Int의 값 수 만큼의 값을 표현할 수 있는것이다.

class Person {
		int age;
		byte type;
		int balance;
}

여러 필드가 들어가는 Tuple, Class, Record등이 모두 곱 타입인 것이다. 그렇기에, Rust로 곱 타입을 생각한다면 Struct, Tuple Struct등을 생각할 수 있다.

struct Person {
	age: u32;
	type: u8;
	balance: i64;
}

struct Person(u32, u8, i64);

이런 점에서, 모든 타입은 집합으로 볼 수 있다. 그리고 이것을 다시 곱 타입과 합 타입으로 구별해서 볼 수 있다.