https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
NullPointerException은 가장 많이 보이고 짜증나는 예외이다.
대부분의 언어에서 null은 아주 흔하게 존재한다.
이 녀석은 대부분 에러를 낼 때만 만나게 되는데 우리는 이 null이 없이는 코딩을 할 수 없을 것 같은 느낌을 가지고 있다.
그 만큼 null은 우리의 코딩방식을 반영한다.
null이라는 건 빈 박스를 취하는 것이다. 무엇을 넣을지는 모르겠지만 일단 취하는 것이다.
그 박스를 제공하기 위해 컴퓨터는 빈 공간을 제공해줄 것이다.
만약 빈 박스에 내용으로 뭔가 하려 한다면? 자바에서는 NullPointerException이 날 것이다.
시대가 조금씩 바뀌고 있다. NULL을 모두 체크해야 하는 것으로는 한계가 있다.
요즘에는 Optional이라는 개념이 많이 유입되고 있는 것 같다.
자바의 경우에도 java.util.Optional 이라는 녀석이 생겼다. 그래서 Optional에서 값이 있는지 없는지를 체크할 수 있도록 한다.
이제 if/else문에 대한 처리를 Optional 내부에서 처리를 할 수 있기 때문에 코드가 복잡할 필요가 없어진다.
package org.test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Optional; import java.util.function.BiFunction; public class TestOptional { // 공백으로 분할하기, 세 개로 분할할 수 없으면 무효! private static Optionalwords(String expr) { String[] res = expr.split(" "); if (3 == res.length) { return Optional.of(res); } else { return Optional.empty(); } } // 문자열을 정수로 변환, 변활할 수 없으면 무효 private static Optional toNum(String s) { try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException ex) { return Optional.empty(); } } private static Optional add(Integer a, Integer b) { return Optional.of(a + b); } private static Optional sub(Integer a, Integer b) { return Optional.of(a - b); } private static Optional mul(Integer a, Integer b) { return Optional.of(a * b); } private static Optional div(Integer a, Integer b) { if (a != b) { return Optional.of(a / b); } else { return Optional.empty(); } } // +-*/ 외 문자 무효 private static Optional >> toBinOp(String s) { switch(s) { case "+" : return Optional.of(TestOptional::add); case "-" : return Optional.of(TestOptional::sub); case "*" : return Optional.of(TestOptional::mul); case "/" : return Optional.of(TestOptional::div); default : return Optional.empty(); } } public static void main(String[] args) throws IOException { String expr = new BufferedReader(new InputStreamReader(System.in)).readLine(); // 마치 haskell의 Monad같은 모습. System.out.println( words(expr) .flatMap(ss -> toNum(ss[0]) .flatMap(a -> toBinOp(ss[1]) .flatMap(op -> toNum(ss[2]) .flatMap(b -> op.apply(a, b))))) .map(n -> "" + n) .orElseGet(() -> "invalid")); } } }
여기서 flatMap이 뭔가 하면 Optional을 받아서 Optional을 리턴하는 녀석인데, Optional의 내용이 empty()면 다음 flatMap으로 전파되지 않는다.
예를들어보자.
Optional저 위에 있는 것들로 테스트를 해보면 empty()를 뱉는 순간 다음 내용으로 if-else문으로 이어지지 않음을 알 수 있다.str = Optional.ofNullable("AB"); //Optional str = Optional.ofNullable("ABC"); //Optional str = Optional.ofNullable("ABCD"); str.flatMap(ss -> { if (ss.length() > 3) {System.out.println("FlatMap1 fail");return Optional.empty();} else {System.out.println("flatMap1 pass"); return Optional.of(ss.toLowerCase());} }).flatMap(ss2 -> { if (ss2.length() > 4) {System.out.println("FlatMap2 fail"); return Optional.empty();} else {System.out.println("FlatMap2 pass");return Optional.of(ss2.toUpperCase() + "@#$");} }).ifPresent(System.out::println);
여기서 알아야 할 점은
NULL 체크를 flatMap에서 전부한다는 것이다. 그런데 온통 flatMap이 있으니!
NULL 체크를 모든 곳에서 하는 것이다.
왜 flatMap이 계속 함수 내부로 형성되는지 의아할 것이다.
각 토큰을 Optional로 리턴한 OPtional 값을 하나하나 검사하려고 flatMap을 사용하는 것이다.
그리고 첫번째 flatMap의 값 ss를 함께 사용하려는 의도도 있을 것이다.
게다가 마지막으로 flatMap은 인스턴스 메소드다. 무슨 말이냐면 객체에서 불러내야 사용할 수 있는 것이다.
Optional 객체가 있을 때, 그 객체에서 flatMap 메소드를 호출해야 가능하다. 그러므로 Optional값이 하나 나오면
그때 검사하고 다른 Optional값이 나오면 또 검사할 flatMap을 그때그때(전부) 호출한다.
이제 이 내용을 하스켈로 보자.
http://www.whynam.com/2018/08/haskell-null-02.html
댓글 없음:
댓글 쓰기