순수 자바의 함수형 프로그래밍:펑터 및 모나드 예제

이 기사는 처음에 리액티브 프로그래밍의 부록이었습니다. 그러나 모나드에 대한 소개는 반응 프로그래밍과 관련이 있지만 그다지 적합하지 않았습니다. 그래서 나는 그것을 꺼내 블로그 게시물로 별도로 게시하기로 결정. 나는”모나드에 대한 내 자신의 반 정확하고 완전한 설명”이 프로그래밍 블로그의 새로운”안녕하세요,세계”라는 것을 알고 있습니다. 그러나이 기사는 자바 데이터 구조 및 라이브러리의 특정 각도에서 펑터와 모나드를 살펴 봅니다. 따라서 나는 그것을 공유 할 가치가 있다고 생각했다.

펑터,모노이드 및 모나드와 같은 매우 기본적인 개념을 기반으로 설계되고 제작되었습니다. 프로그래밍 인터페이스는 응용프로그램이 라디오와 교신할 수 있고 사용중인 라디오에서 필요한 적절한 명령어로 번역하는 일을 합니다.. 당신이 얼마나 컴팩트 한 지 알게 된 후에는 놀라지 말아야합니다. 거의 소수의 핵심 클래스(일반적으로 불변)가 있으며 모든 것은 대부분 순수한 함수를 사용하여 구성됩니다.

최근 스칼라 또는 클로쥬어와 같은 현대 언어로 가장 일반적으로 표현되는 함수형 프로그래밍(또는 기능적 스타일)이 등장하면서 모나드는 널리 논의 된 주제가되었습니다. 그들 주위에 많은 민속이 있습니다:

모나드는 내 기능자 범주의 모노 이드입니다.

제임스 이리

모나드의 저주는 일단 당신이 깨달음을 얻으면,일단 당신이 이해하면-“오,그것이 무엇인지”-당신은 그것을 누구에게나 설명 할 수있는 능력을 잃어 버린다는 것입니다.

더글러스 크록 포드

프로그래머의 대부분,함수 프로그래밍 배경이없는 특히,모나드는 아마도 자신의 프로그래밍 경력에 도움이 될 수 있도록 이론적 일부 비밀 컴퓨터 과학 개념이다 생각하는 경향이있다. 이 부정적인 관점은 수십 개의 기사와 블로그 게시물이 너무 추상적이거나 너무 좁기 때문일 수 있습니다. 그러나 모나드는 특히 자바 개발 키트 이후,심지어 표준 자바 라이브러리에,우리 주변의 모든 것으로 나타났다(그 나중에 더). 절대적으로 훌륭한 것은 일단 모나드를 처음으로 이해하면 갑자기 완전히 다른 목적을 제공하는 몇 가지 관련없는 클래스와 추상화가 익숙해진다는 것입니다.

모나드는 다양한 겉보기에 독립적 인 개념을 일반화하여 모나드의 또 다른 화신을 배우는 데 시간이 거의 걸리지 않습니다. 예를 들어,얼마나 완성되었는지 배울 필요가 없습니다.미래는 자바 8 에서 작동합니다-일단 그것이 모나드라는 것을 알게되면,당신은 그것이 어떻게 작동하는지,그리고 그 의미론에서 무엇을 기대할 수 있는지 정확하게 알 수 있습니다. 그리고 당신은 너무 많은 다른 소리 그러나 관찰 모나드이기 때문에,추가 할 많은이없는 수신 자바에 대해 듣고. 당신이 이미 그것을 모른 채 건너 온 모나드의 수많은 다른 예가 있습니다. 따라서,이 섹션은 실제로 수신자를 사용하지 않을 경우에도 유용한 원기를 회복 될 것입니다.

펑터

모나드가 무엇인지 설명하기 전에 펑터라는 간단한 구조를 살펴 보겠습니다. 펑터는 일부 값을 캡슐화하는 형식화 된 데이터 구조입니다. 구문 관점에서 펑터는 다음과 같은 컨테이너입니다.:

import java.util.function.Function;interface Functor<T> { <R> Functor<R> map(Function<T, R> f);}

그러나 단순한 구문은 펑터가 무엇인지 이해하기에 충분하지 않습니다. 이 함수는 상자 안에 무엇이든을 수신,그것을 변환하고 두 번째 펑터로-있는 그대로 결과를 래핑. 주의 깊게 읽어 보시기 바랍니다. 펑터<티>는 항상 변경할 수없는 컨테이너이므로 맵은 실행 된 원래 객체를 변이시키지 않습니다. 대신,그것은 결과를 반환(또는 결과-인내심)새로운 펑터에 싸여,아마도 다른 유형의 아르 자형. 이러한 패턴은 항상 동일한 펑터 또는 동일한 인스턴스를 반환해야합니다.

종종 펑터<티>이 값과 상호 작용하는 유일한 방법은 그것을 변환하는 것입니다. 그러나 펑터에서 래핑을 풀거나 이스케이프하는 관용적인 방법은 없습니다. 값은 항상 펑터의 컨텍스트 내에 있습니다. 펑터가 유용한 이유는 무엇입니까? 컬렉션,약속,옵션 등과 같은 여러 일반적인 관용구를 일반화합니다. 하나의 균일 한 아핀이 모두 작동합니다. 이 기능을 더 유창하게 만들기 위해 몇 가지 펑터를 소개하겠습니다:

interface Functor<T,F extends Functor<?,?>> { <R> F map(Function<T,R> f);}class Identity<T> implements Functor<T,Identity<?>> { private final T value; Identity(T value) { this.value = value; } public <R> Identity<R> map(Function<T,R> f) { final R result = f.apply(value); return new Identity<>(result); }}

아이덴티티 컴파일을 만들기 위해 추가 에프 유형 매개 변수가 필요했습니다. 위의 예제에서 본 것은 값을 보유하는 가장 간단한 펑터였습니다. 그 값으로 할 수있는 일은 맵 메소드 내에서 변환하는 것이지만 추출 할 방법은 없습니다. 이는 순수 펑터의 범위를 벗어나는 것으로 간주됩니다. 펑터와 상호 작용하는 유일한 방법은 형식 안전 변환 시퀀스를 적용하는 것입니다:

Identity<String> idString = new Identity<>("abc");Identity<Integer> idInt = idString.map(String::length);

또는 유창하게,당신이 기능을 구성하는 것처럼:

Identity<byte> idBytes = new Identity<>(customer) .map(Customer::getAddress) .map(Address::street) .map((String s) -> s.substring(0, 3)) .map(String::toLowerCase) .map(String::getBytes);

이 관점에서 펑터를 통한 매핑은 체인 함수를 호출하는 것과 크게 다르지 않습니다:

byte bytes = customer .getAddress() .street() .substring(0, 3) .toLowerCase() .getBytes();

왜 당신은 심지어 추가 값을 제공하지 않습니다뿐만 아니라,다시 내용을 추출 할 수없는 등의 자세한 포장 귀찮게 것? 글쎄,이 원시 펑터 추상화를 사용하여 몇 가지 다른 개념을 모델링 할 수 있음이 밝혀졌습니다. 예를 들어 자바 8 옵션에서 시작하는 것은 맵()방법을 가진 펑터입니다. 우리가 처음부터 그것을 구현하자:

class FOptional<T> implements Functor<T,FOptional<?>> { private final T valueOrNull; private FOptional(T valueOrNull) { this.valueOrNull = valueOrNull; } public <R> FOptional<R> map(Function<T,R> f) { if (valueOrNull == null) return empty(); else return of(f.apply(valueOrNull)); } public static <T> FOptional<T> of(T a) { return new FOptional<T>(a); } public static <T> FOptional<T> empty() { return new FOptional<T>(null); }}

이제 흥미 롭습니다. FOptional<T>펑터는 값을 보유 할 수 있지만 비어 있을 수도 있습니다. 형식 안전 인코딩 방법null입니다. 값을 제공하거나empty()인스턴스를 만들어FOptional를 구성하는 두 가지 방법이 있습니다. 두 경우 모두Identity과 마찬가지로FOptional는 불변이며 내부의 값과 만 상호 작용할 수 있습니다. FOptional와 다른 점은 변환 함수f이 비어 있으면 어떤 값에도 적용되지 않을 수 있다는 것입니다. 즉,펑터가 정확히 하나의 값T유형을 캡슐화하지는 않을 수 있습니다. List와 같이 임의의 수의 값을 래핑 할 수 있습니다… 펑터:

import com.google.common.collect.ImmutableList;class FList<T> implements Functor<T, FList<?>> { private final ImmutableList<T> list; FList(Iterable<T> value) { this.list = ImmutableList.copyOf(value); } @Override public <R> FList<?> map(Function<T, R> f) { ArrayList<R> result = new ArrayList<R>(list.size()); for (T t : list) { result.add(f.apply(t)); } return new FList<>(result); }}

이 옵션은 다음과 같습니다.: 변환에서 펑터를 사용하지만 동작은 많이 다릅니다. 이제 우리는 선언적으로 전체 목록을 변환,플리스트의 각각의 모든 항목에 변환을 적용합니다. 그래서 만약 당신이 고객의 목록을가지고 그들의 거리의 목록을 원하는,그것은 간단:

import static java.util.Arrays.asList;FList<Customer> customers = new FList<>(asList(cust1, cust2));FList<String> streets = customers .map(Customer::getAddress) .map(Address::street);

고객 컬렉션에 대해getAddress()를 호출할 수 없으며 각 개별 고객에 대해getAddress()를 호출한 다음 컬렉션에 다시 배치해야 합니다. 그건 그렇고,그루비는이 패턴이 너무 일반적이어서 실제로 구문 설탕을 가지고 있음을 발견했습니다:customer*.getAddress()*.street(). 확산 점으로 알려진이 연산자는 실제로 변장map입니다. 아마도 자바 8:list.stream().map(f).collect(toList())에서Stream를 사용하는 대신map내부에서 수동으로list을 반복하는 이유가 궁금 할 것입니다. 이 벨이 울리나요? 자바에서java.util.stream.Stream<T>이 펑터라고 말하면 어떨까요? 그리고 그런데,또한 모나드?
이제 펑터의 첫 번째 이점을 볼 수 있습니다. 마지막 예로Future과 비슷한 약속 펑터를 소개하겠습니다. Promise“약속”은 언젠가 값을 사용할 수있게 될 것입니다. 일부 배경 계산이 생성되었거나 외부 이벤트를 기다리고 있기 때문에 아직 존재하지 않습니다. 그러나 그것은 미래에 언젠가 나타날 것입니다. Promise<T>를 완성하는 역학은 흥미롭지는 않지만 펑터의 성격은:

Promise<Customer> customer = //...Promise<byte> bytes = customer .map(Customer::getAddress) .map(Address::street) .map((String s) -> s.substring(0, 3)) .map(String::toLowerCase) .map(String::getBytes);

익숙한 보인다? 저것은 점 이다! 펑터의 구현은 이 문서의 범위를 벗어나며 중요하지도 않습니다. 우리가 자바 8 에서 완성 된 미래의 구현에 매우 가깝다고 말할 정도로 우리는 거의 엑스 자바에서 관찰 할 수있는 것을 발견했습니다. 그러나 펑터로 돌아 가라. 약속<고객>는 아직 고객의 가치를 보유하지 않습니다. 미래에는 그러한 가치를 가질 것을 약속합니다. 그러나 우리는 여전히 그러한 펑터를 매핑 할 수 있습니다. 이 동작은 펑터가 나타내는 것을 따릅니다. 고객 호출.이는 맵이 비 차단임을 의미합니다. 고객.짧은()고객이 완료 할 것을 약속합니다. 대신 다른 유형의 다른 약속을 반환합니다. 업스트림 약속이 완료되면 다운스트림 약속은 맵()에 전달된 함수를 적용하고 결과를 다운스트림으로 전달합니다. 갑자기 펑터를 사용하면 비 차단 방식으로 비동기 계산을 파이프 라인 할 수 있습니다. 하지만 당신은 이해하거나 배울 필요가 없습니다-약속은 펑터이기 때문에,그것은 구문과 법칙을 따라야합니다.

펑터의 다른 많은 훌륭한 예가 있습니다(예:구성 방식으로 값 또는 오류를 나타냅니다). 그러나 모나드를 볼 때가 왔습니다.

펑터에서 모나드로

펑터가 어떻게 작동하는지,왜 유용한 추상화인지 이해한다고 가정합니다. 그러나 펑터는 예상대로 보편적이지 않습니다. 변환 함수(매핑()에 인수로 전달 된 함수)가 단순한 값이 아닌 펑터 인스턴스를 반환하면 어떻게됩니까? 음,펑터는 단지 값일 뿐이므로 나쁜 일은 일어나지 않습니다. 반환 된 것은 무엇이든 펑터에 다시 배치되므로 모두 일관되게 작동합니다. 그러나 문자열을 구문 분석하는 데 편리한 방법이 있다고 상상해보십시오:

FOptional<Integer> tryParse(String s) { try { final int i = Integer.parseInt(s); return FOptional.of(i); } catch (NumberFormatException e) { return FOptional.empty(); }}

예외는 유형 시스템 및 기능적 순도를 저해하는 부작용입니다. 순수 함수형 언어에서는 예외를 위한 장소가 없습니다. 결국,우리는 수학 수업 중에 예외를 던지는 것에 대해 들어 본 적이 없습니다. 오류 및 잘못된 조건은 값과 래퍼를 사용하여 명시 적으로 표시됩니다. 예를 들어,트리파시()는 문자열을 가져오지만,단순히 인터프리터를 반환하거나 런타임에 예외를 자동으로 던지지는 않습니다. 형식 시스템을 통해 트립 파싱()이 실패 할 수 있으며 잘못된 형식의 문자열을 갖는 데 예외적이거나 잘못된 것은 없습니다. 이 반 실패는 선택적 결과로 표시됩니다. 흥미롭게도 자바 예외,선언 하 고 처리 해야 하는 것 들을 확인 했다,그래서 어떤 의미에서,자바는 그 점에서 순수,그것은 부작용을 숨기지 않습니다. 그러나 좋든 나쁘 든간에 체크 된 예외는 종종 자바에서 권장되지 않으므로 다시 트리 파스()로 돌아가 보겠습니다. 이미 옵션으로 싸인 문자열로 트리 파스를 구성하는 것이 유용 할 것 같습니다:

FOptional<String> str = FOptional.of("42");FOptional<FOptional<Integer>> num = str.map(this::tryParse);

그것은 놀람으로 와서는 안됩니다. tryParse()int을 반환하면FOptional<Integer> num을 얻을 수 있지만map()함수가FOptional<Integer>자체를 반환하기 때문에 어색한FOptional<FOptional<Integer>>로 두 번 래핑됩니다. 유형을주의 깊게 봐 주시기 바랍니다,우리는 여기에이 더블 래퍼를 가지고 왜 당신은 이해해야합니다. 끔찍한 것 외에도 펑터에 펑터가 있으면 구성 및 유창한 체인이 가능합니다:

FOptional<Integer> num1 = //...FOptional<FOptional<Integer>> num2 = //...FOptional<Date> date1 = num1.map(t -> new Date(t));//doesn't compile!FOptional<Date> date2 = num2.map(t -> new Date(t));

여기서 우리는int을+날짜+로 바꾸어FOptional의 내용을 매핑하려고합니다. int -> Date의 함수를 사용하면Functor<Integer>에서Functor<Date>으로 쉽게 변환 할 수 있습니다. 그러나num2의 경우 상황이 복잡해집니다. num2.map()이 입력으로 수신하는 것은 더 이상int이 아니라FOoption<Integer>이며 분명히java.util.Date에는 이러한 생성자가 없습니다. 우리는 그것을 두 번 감싸서 펑터를 부러 뜨 렸습니다. 그러나 단순한 값이 아닌 펑터를 반환하는 함수를 갖는 것은 너무 일반적이어서(tryParse()과 같이)그러한 요구 사항을 무시할 수 없습니다. 한 가지 방법은 중첩 된 펑터를”평평하게”하는 매개 변수가없는 특수join()메서드를 도입하는 것입니다:

FOptional<Integer> num3 = num2.join()

작동하지만이 패턴이 너무 일반적이기 때문에flatMap()이라는 특수 메소드가 도입되었습니다. flatMap()map과 매우 유사하지만 인수로 수신 된 함수가 펑터 또는 모나드를 정확하게 반환 할 것으로 기대합니다:

interface Monad<T,M extends Monad<?,?>> extends Functor<T,M> { M flatMap(Function<T,M> f);}

우리는 단순히flatMap가 더 나은 구성을 허용하는 구문 설탕 일 뿐이라는 결론을 내 렸습니다. 그러나flatMap방법(하스켈에서bind또는>>=이라고도 함)은 복잡한 변환을 순수하고 기능적인 스타일로 구성 할 수 있기 때문에 모든 차이를 만듭니다. FOptional가 모나드 인스턴스 인 경우 구문 분석이 갑자기 예상대로 작동합니다:

FOptional<String> num = FOptional.of("42");FOptional<Integer> answer = num.flatMap(this::tryParse);

모나드는map을 구현할 필요가 없으며flatMap()위에 쉽게 구현할 수 있습니다. 사실flatMap는 완전히 새로운 변환 우주를 가능하게하는 필수 연산자입니다. 분명히 펑터와 마찬가지로 구문 준수는 일부 클래스를 모나드라고 부르기에 충분하지 않으며flatMap()연산자는 모나드 법칙을 따라야하지만flatMap()의 연관성 및 정체성과 같이 상당히 직관적입니다. 후자는m(x).flatMap(f)x값과f함수를 보유하는 모든 모나드에 대해f(x)와 동일해야합니다. 우리는 모나드 이론에 너무 깊이 빠져들지 않을 것이며,대신 실용적인 의미에 초점을 맞추자. 모나드는 내부 구조가 사소하지 않을 때 빛납니다(예:Promise모나드). 다음 프로그램에서Promise이 어떻게 작동 할 것인지 유형 시스템에서 추측 할 수 있습니까? 첫째,잠재적으로 완료하는 데 시간이 걸릴 수있는 모든 메서드는Promise:

import java.time.DayOfWeek;Promise<Customer> loadCustomer(int id) { //...}Promise<Basket> readBasket(Customer customer) { //...}Promise<BigDecimal> calculateDiscount(Basket basket, DayOfWeek dow) { //...}

이제 모나드 연산자를 사용하여 이러한 함수를 모두 차단하는 것처럼 구성 할 수 있습니다:

Promise<BigDecimal> discount = loadCustomer(42) .flatMap(this::readBasket) .flatMap(b -> calculateDiscount(b, DayOfWeek.FRIDAY));

이 흥미로운된다. flatMap()는 모나드 유형을 유지해야하므로 모든 중간 객체는Promise초입니다. 그것은 단지 순서대로 유형을 유지하는 것에 관한 것이 아닙니다-선행 프로그램은 갑자기 완전히 비동기 적입니다! loadCustomer()Promise을 반환하므로 차단되지 않습니다. readBasket()Promise이 가지고있는 것을 취하고 다른Promise을 반환하는 함수를 적용합니다. 기본적으로,우리는 백그라운드에서 한 단계의 완성이 자동으로 다음 단계를 트리거 계산의 비동기 파이프 라인을 구축.

플랫 맵 탐색()

두 개의 모나드를 가지고 함께 묶인 값을 결합하는 것은 매우 일반적입니다. 그러나 펑터와 모나드 모두 내부에 직접 액세스 할 수 없으므로 불순합니다. 대신 모나드를 벗어나지 않고 신중하게 변환을 적용해야합니다. 두 모나드가 있고 당신이 그들을 결합 할 상상:

import java.time.LocalDate;import java.time.Month;Monad<Month> month = //...Monad<Integer> dayOfMonth = //...Monad<LocalDate> date = month.flatMap((Month m) -> dayOfMonth .map((int d) -> LocalDate.of(2016, m, d)));

위의 의사 코드를 연구 할 시간을 가지십시오. 핵심 개념을 강조하기 위해Promise또는List와 같은 실제 모나드 구현을 사용하지 않습니다. 우리는 두 개의 독립적 인 모나드를 가지고 있는데,하나는Month유형이고 다른 하나는Integer유형입니다. 그 중LocalDate을 빌드하려면 두 모나드의 내부에 액세스 할 수있는 중첩 변환을 빌드해야합니다. 특히 한 곳에서flatMap를 사용하고 다른 곳에서map()을 사용하는 이유를 이해했는지 확인하십시오. 세 번째Monad<Year>도 있다면 이 코드를 어떻게 구성할지 생각해 보십시오. 두 개의 인수(우리의 경우md)의 함수를 적용하는이 패턴은 매우 일반적이므로이 변환을 정확히 수행하는liftM2이라는 특수 도우미 함수가 있습니다mapflatMap위에 구현됩니다. 자바 의사 구문에서는 다음과 같이 다소 보일 것입니다:

Monad<R> liftM2(Monad<T1> t1, Monad<T2> t2, BiFunction<T1, T2, R> fun) { return t1.flatMap((T1 tv1) -> t2.map((T2 tv2) -> fun.apply(tv1, tv2)) );}

모든 모나드에 대해이 방법을 구현할 필요는 없으며flatMap()으로 충분하며 모든 모나드에 대해 일관되게 작동합니다. liftM2은 다양한 모나드와 함께 사용할 수있는 방법을 고려할 때 매우 유용합니다. 예를 들어listM2(list1, list2, function)list1list2(데카르트 곱)에서 가능한 모든 항목 쌍에function을 적용합니다. 다른 한편으로,옵션의 경우 두 옵션이 비어 있지 않은 경우에만 함수를 적용합니다. 더 좋은 점은Promise 모나드의 경우Promise이 모두 완료되면 함수가 비동기 적으로 실행됩니다. 즉,두 개의 비동기 단계의 간단한 동기화 메커니즘(포크 조인 알고리즘에서join())을 발명했습니다.

flatMap()위에 쉽게 구축 할 수있는 또 다른 유용한 연산자는filter(Predicate<T>)입니다.이 연산자는 모나드 내부의 모든 것을 가져 와서 특정 술어를 충족시키지 못하면 완전히 버립니다. 어떤면에서는map과 비슷하지만 1 대 1 매핑이 아니라 1 대 0 또는 1 이 있습니다. 다시filter()는 모든 모나드에 대해 동일한 의미를 지니지 만 실제로 사용하는 모나드에 따라 매우 놀라운 기능을 가지고 있습니다. 분명히 목록에서 특정 요소를 필터링 할 수 있습니다:

FList<Customer> vips = customers.filter(c -> c.totalOrders > 1_000);

하지만 옵션처럼 잘 작동합니다. 이 경우 옵션의 내용이 일부 기준을 충족하지 않으면 비어 있지 않은 옵션을 빈 옵션으로 변환 할 수 있습니다. 빈 옵션은 그대로 유지됩니다.

모나드 목록에서 모나드 목록

플랫 맵()에서 유래 한 또 다른 유용한 연산자는 시퀀스()입니다. 당신은 쉽게 단순히 유형 서명을보고 무엇을 추측 할 수있다:

Monad<Iterable<T>> sequence(Iterable<Monad<T>> monads)

종종 우리는 같은 유형의 모나드를 잔뜩 가지고 있으며 그 유형의 목록의 단일 모나드를 갖고 싶습니다. 이것은 당신에게 추상적으로 들릴 수도 있지만 인상적으로 유용합니다. 각 호출은Promise<Customer>를 반환합니다. 이제Promise의 목록이 있지만 실제로 원하는 것은 고객 목록(예:웹 브라우저에 표시)입니다. 사용 사례에 따라sequence()concat()또는merge()이라고 합니다.:

FList<Promise<Customer>> custPromises = FList .of(1, 2, 3) .map(database::loadCustomer);Promise<FList<Customer>> customers = custPromises.sequence();customers.map((FList<Customer> c) -> ...);

고객 신분증을 나타내는FList<Integer>이 그 위에 있습니다(FList가 펑터 인 데 어떻게 도움이되는지 알 수 있습니까?)각 아이디에 대해database.loadCustomer(id)으로 전화하십시오. 이Promise의 다소 불편한 목록으로 이어집니다.sequence()은 하루를 저장하지만,다시 한번 이것은 단지 통사론적인 설탕이 아닙니다. 위의 코드는 완전히 차단되지 않습니다. 다른 종류의 모나드의 경우sequence()은 여전히 의미가 있지만 다른 계산 컨텍스트에서는 의미가 있습니다. 예를 들어FList<FOptional<T>>FOptional<FList<T>>로 변경할 수 있습니다. 그런데flatMap()위에sequence()(map()과 같이)을 구현할 수 있습니다.

이것은 일반적으로flatMap()과 모나드의 유용성에 관해서 빙산의 일각에 불과합니다. 다소 모호한 범주 이론에서 왔음에도 불구하고 모나드는 자바와 같은 객체 지향 프로그래밍 언어에서도 매우 유용한 추상화로 판명되었습니다. 모나드를 반환하는 함수를 작성할 수 있다는 것은 보편적으로 도움이되므로 수십 개의 관련없는 클래스가 모나드 동작을 따릅니다.

또한 모나드 내부에 데이터를 캡슐화하면 명시 적으로 데이터를 추출하기가 어렵습니다. 이러한 작업은 모나드 동작의 일부가 아니며 종종 비 관용적 코드로 이어집니다. 예를 들어Promise<T>Promise.get()는 기술적으로T을 반환 할 수 있지만 블로킹을 통해서만 반환 할 수 있지만flatMap()을 기반으로 한 모든 연산자는 비 블로킹입니다. 또 다른 예는FOptional.get()이지만FOptional가 비어 있기 때문에 실패 할 수 있습니다. 목록에서 특정 요소를 엿보는FList.get(idx)조차도for루프를map()로 자주 바꿀 수 있기 때문에 어색하게 들립니다.

요즘 모나드가 왜 그렇게 인기가 있는지 이해하기를 바랍니다. 심지어 자바와 같은 객체 지향(틱)언어,그들은 매우 유용한 추상화입니다.

답글 남기기

이메일 주소는 공개되지 않습니다.