함수형 프로그래밍 언어에서는 Exception과 Null을 사용하지 않는다. 대신, Result(또는 Try)와 Option(또는 Optional)을 사용한다.
다음과 같은 자바 함수가 있다고 하자:
int getIndexOf(int idx) {
if (idx < 0) throw new IndexOutOfBoundsException()
;
if (globalIntList == null) throw new NullPointerException();
if (globalIntList.size() <= idx) throw new Exception();
return globalIntList[idx];
}
위의 함수는 기본적으로 int 타입을 리턴한다. 그러나, 실제 함수의 작동을 보면 IndexOutOfBoundsException
, NullPointerException
, Exception
과 int
를 리턴한다. 실제 리턴되는 타입과 함수에 표기된 리턴 타입이 불일치한다. 그러므로 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);
이런 점에서, 모든 타입은 집합으로 볼 수 있다. 그리고 이것을 다시 곱 타입과 합 타입으로 구별해서 볼 수 있다.