'모나드'에 해당되는 글 1건

  1. 2021.07.23 C# - 함수형 프로그래밍, 모나드 패턴
728x90

패턴 매칭이란 호출해야 할 함수의 변형을 정확하게 선택하는 디스패치의 유형이다

 

입력 값을 전달하고 정확한 선택을 결정하는 if 조건식의 개념으로 생각하면 되는것 같다.

 

c#의 Nullable<T>, IEnumerable<T>, Func<T>, Lazy<T>, Task<T> 형식은 모나드라는 개념을 구현하고 있다.

 

1. 모나드는 다른 타입을 인자로 받는 타입이다.

모나드는 기본적으로 int나 string 같은 타입이고, 타입을 인자로 받는다고 한다.

c++의 템플릿, c# 제네릭 클래스를 생각하면 되고, 이 T라는 타입의 인자를 받는 모나드 M을 M[T] 라고 표현을 한다.

return operator 혹은 unit operator라고 부른다.

 

2. 모나드는 타입의 값을 생성하는 함수가 있어야 한다

모나드는 임의 타입의 값을 받아서 그 타입을 인자로 받은 모나드 타입의 값을 반환하는 함수가 있어야 한다.

T 타입의 값을 받아서 M[T] 타입의 값을 반환하는 함수가 있어야 한다.

보통 bind operator라고 부른다.

 

3. 다른 모나드 타입으로 진행하는 함수가 있어야 한다.

M[T] 타입의 모나드가 있을 때 T타입의 변수를 받아 M[U] 타입의 모나드를 반환하는 함수를 받아서, M[U] 타입의 값을 반환하는 함수다.

이 함수를 통해서 모나드에서 다른 모나드로 진행 할 수 있다.

 

위 3가지 조건을 만족해야 모나드라는 패턴이 만족 된다고 한다. 

 

...........프로그래머 로써는 이정도로만 이해해도 된다고 하는데 그래도 이게 무슨 말인지 알수가 없구나...

 

사용 예제 : Option : 존재하지 않음을 표현하기 

(출처 : https://blog.seulgi.kim/2015/07/monad-option.html)

Option 타입이 가장 기본적인 모나드이고, 많이 사용되는 모나드라고 한다.

 

Option 타입이 해결하고자 하는 문제는 값이 존재하지 않음을 런타임 에러가 발생할 가능성 없이 표현하는 것이다.

 

C++, C# 등 기존의 많은 언어는 값이 존재하지 않음을 표현하기 위해서 null point를 사용하는데 이 null point는 컴파일 타임에 잡을 수 없는 nullpointerException을 발생 시키기 떄문에 조심해야 한다.

 

이런 문제를 null object pattern 같은 패턴을 이용하거나 null check를 한 겹 감싼 클래스를 만들거나 해서 보완하지만 문제를 완벽하게 해결할 수는 없다

 

하지만 Option 타입은 이에 대한 해결책을 제공한다.

 

Option 타입은 하나의 타입 파라미터를 받아, 그 타입의 값을 가지고 있을 수도 있고, 없을 수도 있다. Int 타입을 타입 파라미터로 받았다면, 타입은 Option[Int]가 되며, String 타입을 타입 파라미터로 받았다면, Option[String]이 된다. 즉, T 타입을 타입 파라미터로 받은 Option은 Option[T]가 된다. 이를 간단히 표현하기 위해서 T?같은 방식으로 표현하기도 한다.

Option[T] 타입의 값은 T 타입의 값을 가지고 있을 수도 있고, 아무런 값이 없을 수도 있다. 이렇게 말하면 단순한 nullable과 다를 게 없어 보인다. 하지만 Option은 두 상태를 다른 타입으로 분리함으로써 nullable보다 안전한 방법을 제공한다.

Option 타입은 두 타입의 sum type이다. 하나는 값이 존재하지 않음을 나타내는 None이라는 타입이고, 다른 하나는 무언가 값이 있음을 나타내는 Some이라는 타입이다. sum type을 지원하는 F#, rust, Haskell 같은 언어에서는 이를 sum type으로 표현하고, Scala나 전통적인 객체지향 언어에서는 Option이라는 interface의 구현체로 Some과 None이 있는 것으로 표현한다.

 
None은 아무런 값도 가지고 있지 않음을 나타내는 타입이다. None 타입의 값에 bind operator를 호출해도 아무 일도 일어나지 않는다. 인자로 넘겨진 함수는 실행되지 않고, bind operator의 결과는 언제나 None이다.
 
 Some 타입은 무언가 값을 가지고 있음을 나타내는 타입이다. 어떤 타입의 값을 가졌는지 나타내기 위해 타입 파라미터를 받는다. T 타입의 값을 가지고 있는 Some 타입은 Some[T]라고 표현한다. Some[T] 타입은 반드시 T 타입의 값을 들고 있어야 한다. Some 타입이면서 내부적으로 값을 들고 있지 않는 상황은 올 수 없다.1) Some타입은 반드시 값을 가지고 있기 때문에 bind operator는 언제나 NullPointerException 없이 원하는대로 실행된다.
 

책을 보면서도 처음 보는 개념이라 많이 이해하기 힘들고..

내가 이해한 것이 맞는건지는 모르겠지만. 정리를 해보자면

 

모나드가 바라는 것은 함수 A의 결과를 함수 B에 전달해서 수행을 하고 싶은데 

함수마다 정의된 리턴값 타입이 다를수도 있고, 

A의 결과를 특정 공간에 저장한 다음, 함수 B가 해당 공간에 접근해서 수행을 진행하는 방법이 있다고 한다면

이 방법은 A, B 함수가 아닌 또다른 외부 함수가 공간에 저장할 가능성이 있고 그렇다면 완벽한 결과를 보장할 수 없기 때문에 함수형 프로그래밍에서 원하는 방향과는 맞지 않는다는 것이고

이것을 해결하기 위해 함수 A와 B를 합친다? 라고 해야할까,, 하나의 함수인 것처럼 동작하게 하는 함수를 만든다는 것이 모나드의 개념인 거 같다.

 

추가 한줄 요약 정리 : <T> 타입의 Functor를 <R> 타입의 Functor로 바꾸는 기능

 

책에있는 간단한 예제로 보면

string타입을 받아서 string에 해당하는 지정된 숫자 int 타입을 반환하는 함수가 있다

private static Nullable<int> WordToNumber(string word)
{
  Nullable<int> returnValue;
  if ( word == null ) return null;
  switch (word.ToLower())
  {
    case "zero" : returnValue = 0; break;
    case "one" : returnValue = 1; break;
                     .....
    default: returnValue = null;
  }
int 형식은 null을 처리하지 못하지만, Nullable<int>를 사용해서 null을 반환 할수있게 한것이다.

 

WordToNumber에는 우리가 따로 if문을 사용해서 if (word == null) 이냐 인것처럼 null 체크가 불필요하다.


null일 경우에는 그냥 null을 반환하고 값이 null이면 로직이 실행이 되지 않는다거나 하게끔 로직을 짜서 타입에 대한 안정성을 유지 할 수 있다는 것


이런 예외적인 값에 대한 컨트롤이 가능한 로직을 짤수 있다는 방식 때문에
비동기 데이터를 다루는 로직에 있어서도 값이 이미 존재하는 것 처럼 로직을 짤 수 있을것 같다.


중간에 예외 발생 없이 함수형 프로그래밍에서 추구하는 입력에 따른 결과를 반환하는 것을 보장하는 방법이 되는 것이 모나드 패턴인거 같다.

 

혼자 프로그래밍 작업을 하는 환경이라면 모르겠지만.. 여러 사람들과 같이 하는 작업중에 이러한 패턴을 이용해서 함수를 설계 할때는 사전에 협의가 잘 되어 있어야 가능할 것 같고, 

합친다, 확장한다라는 개념은 물론 좋지만 이것도 결국엔 몸집이 계속해서 커져 나간다면 .. 어떻게 관리되어지고 어떻게 유지 보수를 해 나갈지, 이런 걱정도 많이 해보게 될 것 같다. 

 

 

728x90
Posted by 정망스
,


맨 위로
홈으로 ▲위로 ▼아래로 ♥댓글쓰기 새로고침