Search for a command to run...
함수형 프로그래밍 언어에서는 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);
}
💡 엄밀하게 말하면
Unit,Void는 어떤 값을 가진다
대수적 타입에서는 모든 타입을 타입의 합(Sum) 또는 곱(Product)으로 볼 수 있다고 생각한다. 예를들어 Byte (1Byte = -128~127) 타입을 생각해 보자. 값 중 하나로 생각할 수 있다.
💡 타입의 최소 단위는 값 자체가 될 수 있다.
예를 들어서String a = "가나다라"라는 Java 코드가 있다고 하자. 이 값의 가장 큰 타입 범위는Object가 된다. 그리고Serializable,Comparable<String>,CharSequence수준의 타입을 거쳐서, 우리가 자주 사용하는String타입이 된다. 그러나“가나다라”자체도 타입이 될 수 있다.
그렇기 때문에, 처음 나온 함수 자체를 보면 다음과 같은 리턴 타입으로 이해할 수 있다: 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);
이런 점에서, 모든 타입은 집합으로 볼 수 있다. 그리고 이것을 다시 곱 타입과 합 타입으로 구별해서 볼 수 있다.
그러면 아래의 retrivePerson 함수는 어떻게 대수적 타입으로 바라볼 수 있을까?
class Person {
int age;
byte type;
int balance;
}
Person retrievePerson(String PersonalId) {
Person data = ~~~;
return data;
}
답은 (int * byte * int) + null 이다. 즉, 이 함수는 4,722,366,482,869,600,124,929 개의 값을 표현할 수 있다. 위의 retrivePerson 함수는 null이 리턴 될 수 있다. 그렇기에, 리턴 타입이 Person이라고 하면 그 아래에는 Null이 포함되어 있다는 것을 인지 해야 한다.
그러나 타입의 입장에서, Person 과 Person | Null 은 매우 다르다. 후자는 PersonNullable 과 같이 표현 하는것이 옳을 것이다. 그러나 최근까지도 Null은 기저에 깔려있다고 생각하며 Person 타입으로만 생각했다. 그나마 최근 언어에서 ? 을 이용해서 Null이 합쳐져 있음을 표현하기 시작했다. 아래의 코드는 Person 과 Null 타입을 리턴할 수 있음을 알려준다.
fun retrivePersion(PersonalId: String) : Person? {
return ~~~
}
만약 ?를 붙이지 않는다면 Null을 리턴할 수 없다.
// 오류 발생
fun retivePersion(PersionlId: String) : Person {
return null;
}
Person 타입은 Null을 포함할 수 없기 때문이다.